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

得到如下的结果

4334d757908d6f7c5bd4cf62ff148a43.png

当程序中有非标准库中的头文件时,只编译cpp文件会显示找不到头文件,所以需要用到参数 -i

命令格式

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

其他参数列表

a2096a5e67ded2465d2d6b0dd124f0d0.png

注意:包含头文件的参数-I是大写的i,指定库名的参数是小写的L,不要搞混了。

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

g++ -c a.cpp

3.用makefile写编译指令

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

以一个简单的情况为例,现在有如下六个文件

a58407c3efef9f4c064f9cd432886906.png

整个编译过程应该是如下命令:

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中剔除掉http://a.cc和http://b.cc

2. @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。
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐