zl程序教程

您现在的位置是:首页 >  系统

当前栏目

linux学习3(C++程序编译及makefile编写)

LinuxC++学习程序 编译 编写 Makefile
2023-09-14 09:07:09 时间

1.最基本的编译过程

此时有一个a.cpp文件,文件中内容如下:

#include <iostream>
using namespace std;
int main()
{
    cout<<"hello world"<<endl;
    return 0;
}

第一步:预处理,将所有的#include头文件以及宏定义替换成其真正的内容,输入命令

g++ -E a.cpp >> a.i

如果不输入>>a.i,只会在终端中显示预处理以后的结果,不会生成.i文件

第二步:编译,将经过预处理之后的程序转换成特定汇编代码(assembly code)的过程,输入命令

g++ -S a.i

这样就可以生成汇编的.s文件

这里有个参数-o,如果不加-o,则生成的文件名为cpp的文件名,如果不想要这个文件名,就可以使用如下命令,生成想要的目标文件名

g++ -S a.i -o xxx

第三步:汇编,汇编过程将上一步的汇编代码转换成机器码(machine code),也就是二进制。输入命令

g++ -c a.s

这样就可以生成二进制的.o文件

这里有个参数-o,如果不加-o,则生成的文件名为cpp的文件名,如果不想要这个文件名,就可以使用-o参数,生成想要的目标文件名(同上)

第四步:链接,链接过程将多个目标文以及所需的静态链接库文件(.a等)链接成最终的可执行文件(executable file)。输入命令

g++ a.o

生成可执行文件

这里有个参数-o,如果不加-o,则生成的文件名为a.out,如果不想要这个文件名,就可以使用-o参数,生成想要的目标文件名(同上)

注意:并不是需要严格执行每一步才可以,比如想直接生成汇编文件,也就是.s文件。可以直接使用命令

g++ -S a.cpp

相当于系统帮你自动执行了预处理步骤,生成.s文件。

2.常用编译方法

在第一点中讲到的整个编译过程并不是每次编译都需要用到,一般常用的是直接生成可执行文件,比如

g++ a.cpp -o helloworld

然后输入

./helloworld

得到如下的结果
在这里插入图片描述
当程序中有非标准库中的头文件时,只编译cpp文件会显示找不到头文件,所以需要用到参数 -i

命令格式

g++ hello.cpp -o hello -I (路径)

其他参数列表
在这里插入图片描述
注意:包含头文件的参数-I是大写的i,指定库名的参数是小写的L,不要搞混了。

另一个常用命令是输出二进制文件.o的命令,由于在制作动态和静态库时,需要使用到这个。

g++ -c a.cpp

3.用makefile写编译指令

简单的makefile主要掌握1条规则,2个函数,3个变量即可

以一个简单的情况为例,现在有如下六个文件
在这里插入图片描述
整个编译过程应该是如下命令:

gcc -c itcast_asn1_der.c -o itcast_asn1_der.o        预处理、编译、汇编

gcc -c itcastderlog.c -o itcastderlog.o            预处理、编译、汇编

gcc -c keymng_msg.c -o keymng_msg.o             预处理、编译、汇编

gcc -c keymng_msg_test.c -o keymng_msg_test.o        预处理、编译、汇编

gcc itcast_asn1_der.o itcastderlog.o keymng_msg.o keymng_msg_test.o -o a.out    链接

1 条规则:

目标:依赖

    命令

要求,目标必须晚于依赖条件生成时间。如果不满足,则更新目标。

                    如果依赖不存在,寻找新的规则生成依赖。

将如上的五条命令生成五条规则如下:

itcast_asn1_der.o:itcast_asn1_der.c
    gcc -c itcast_asn1_der.c -o itcast_asn1_der.o

itcastderlog.o:itcastderlog.c
    gcc -c itcastderlog.c -o itcastderlog.o

keymng_msg.o:keymng_msg.c
    gcc -c keymng_msg.c -o keymng_msg.o

keymng_msg_test.o:keymng_msg_test.c
    gcc -c keymng_msg_test.c -o keymng_msg_test.o

a.out:itcast_asn1_der.o itcastderlog.o keymng_msg.o keymng_msg_test.o
    gcc itcast_asn1_der.o itcastderlog.o keymng_msg.o keymng_msg_test.o -o a.out   

2 个函数:

$(wildcard 参):    获取指定类型特征的文件、

    src = $(wildcard *.c) 

$(patsubst 参1, 参2, 参3):根据执行类型变量,获取新变量。

    $(patsubst %.c, %, $(src)): 将参数3 中,包含参数1的部分,替换成参数2。

对应到例子中

#src = itcast_asn1_der.c itcastderlog.c keymng_msg.c keymng_msg_test.c
src = $(wildcard *.c)            
#obj = itcast_asn1_der.o itcastderlog.o keymng_msg.o keymng_msg_test.o
obj = $(patsubst %.c, %.o, $(src))
    

3 个自动变量:

$@:    在规则的 命令中, 表示目标。

$^:    在规则的 命令中, 表示 所有依赖条件

$<:    在规则的 命令中, 表示 第一个依赖条。 如果是模式规则,会将依赖条件依次取出。 执行命令。

4个符号:

= 是最基本的赋值
 
:= 是覆盖之前的值
 
?= 是如果没有被赋值过就赋予等号后面的值
 
+= 是添加等号后面的值

可以参考博客:https://blog.csdn.net/b876144622/article/details/80372161

其他关键词:

  1. filter-out可以把一些源文件剔除

比如:

FILES := $(wildcard $(PATH)/src/*.cc)//现获得所有的源文件
SRC_FILES += $(filter-out $(PATH)/src/a.cc $(PATH)/src/b.cc, $(FILES))//剔除一些不想要的源文件

注意:filter-out的语法是filter-out 剔除的内容, 剔除的目标文件

上面那个例子中,就是从FILES中剔除掉a.cc和b.cc

  1. @echo可以查看makefile中的变量
debug:
	@echo $(srcc)#显示srcc变量的内容

可以利用刚才的规则将五条规则简化为如下两条规则

#表示终极目标
ALL:app

#使用变量的方式$(变量名),下面这条是把所有的.c文件变成.o文件
app:$(obj)
    gcc $^ -o $@
#这句话是将所有的.c文件生成.o文件,前面加$(obj)是静态模式规则,obj变量会执行这个模式规则,而其他变量则不会执行这一规则,模式规则就是将所有满足条件的文件依次取出执行这一语句,%.o是目标文件,%.c是依赖文件
$(obj):%.o : %.c
    gcc -c $< -o $@ 

注意:这里的模式规则是指如果目标有多个,并且依赖条件也不一样的情况下,如果依赖条件和目标在名称上有对应关系,可以把多个目标生成语句合并为一个条规则。具体可以看下面这个例子

下面是将c文件放到src文件夹下,头文件放到inc文件夹下,并要求生成的文件放到obj文件夹下,生成的最终的makefile编译文件

#src = itcast_asn1_der.c itcastderlog.c keymng_msg.c keymng_msg_test.c
src = $(wildcard ./src/*.c)			
#obj = itcast_asn1_der.o itcastderlog.o keymng_msg.o keymng_msg_test.o
obj = $(patsubst ./src/%.c, ./obj/%.o, $(src))
#百分号就类似shell指令当中的通配符*,在makefile中这两种通配符都可以使用,但有些情况下不能使用,可以参考这篇文章:https://www.cnblogs.com/warren-wong/p/3979270.html
target = app
inc_path = ./inc
#上面是定义的两个变量
#表示终极目标
ALL:$(target)
 
#使用变量的方式$(变量名),下面这条是把所有的.c文件变成.o文件
$(target):$(obj)
	gcc $^ -o $@
#模式规则开头不仅仅有目标文件和依赖文件,还会多一个目标集合(个人理解)
#前面加$(obj)就是目标文件集合,将所有满足条件的文件依次取出执行这一语句,
#./obj/%.o是目标文件,./src/%.c是依赖文件,并且其中%是一样的
#其中gcc -c itcast_asn1_der.c -o itcast_asn1_der.o -I ./inc就是这个目标规则中会执行的一句命令
$(obj):./obj/%.o : ./src/%.c
	gcc -c $< -o $@ -I $(inc_path)
 
clean:
	-rm -rf $(obj) $(target)
 
.PHONY: clean ALL
#表示clean和ALL是一种声明符号,不是规则中的目标文件

如果不止是c源文件,还有C++源文件,makefile如下

srcc = $(wildcard ./src/*.c)	
 
srccpp = $(wildcard ./src/*.cpp)			
 
objc = $(patsubst ./src/%.c, ./obj/%.o, $(srcc))
 
objcpp = $(patsubst ./src/%.cpp, ./obj/%.o, $(srccpp))
 
target = app
inc_path = ./inc
 
 
ALL:$(target)
 
#多个依赖可以直接在后面接着写
$(target):$(objc) $(objcpp)
	g++ $^ -o $@
 
$(objc):./obj/%.o : ./src/%.c
	gcc -c $< -o $@ -I $(inc_path)
 
$(objcpp):./obj/%.o : ./src/%.cpp
	g++ -c $< -o $@ -I $(inc_path)
 
clean:
	-rm -rf $(objc) $(objcpp) $(target)
 
debug:
	@echo $(srcc)#显示srcc变量的内容
 
.PHONY: clean ALL

注意:

  • 凡是一个名称加冒号,这一行后面就没有的,都是自定义的命令,比如clean,debug(但是ALL特殊),使用的方法就是在终端输入make
    +名称,比如make clean,make debug。但是ALL并不是命令,而是指终极目标,我理解是Makefile文件只能生成一个目标,如果有多个,就是设定一个ALL目标,让这些目标都成为这个ALL目标的依赖即可。并且ALL也不符合命令的格式。
  • .PHONY:后面跟的是目标,是可以重复生成的,比如ALL不加在后面,那么如果已经编译生成了ALL,那么如果ALL没有删除,就不可以再次编译ALL。