g++可以用于编译C++代码生成可执行程序,从原始代码到生成可执行过程中实际经历了以下4个步骤:
1. 预处理:宏替换,注释消除,查找相关库文件等[使用-E参数]。
# 只激活预处理,不会自动生成.i文件,如果需要可以通过>重定向生成hello.i。
g++ -E hello.cpp > hello.i
2,编译:将预处理后的文件转换成汇编语言,生成.s汇编文件[使用-S参数]。
# 两种方式:
# cpp源文件直接经过预处理,编译生成.s汇编文件
g++ -S hello.cpp
# 将预处理生成的.i文件编译生成.s汇编文件
g++ -S hello.i
3,汇编:将汇编文件转换为目标文件(机器代码),即.o文件[使用-c参数]。
# 生成hello.o文件,以下几种都可以。
g++ -c hello.cpp # 实际执行了预处理,编译,汇编
g++ -c hello.i # 实际执行了编译,汇编
g++ -c hello.s # 实际执行了汇编
4,链接:链接相关目标文件或动静态库等,生成可执行文件[使用-o参数]。
# -o用于指定可执行文件名
g++ hello.cpp # 默认生成a.out可执行文件,一步到位
g++ hello.cpp -o hello #生成可执行文件,指定文件名为hello。
g++ hello.o -o hello #基于hello.o目标文件生成可执行文件。
当存在多个头文件和源文件时,需要先把每个文件都生成.o目标文件,最后链接成可执行。但是在一个大型的项目中,存在大量的文件,靠这种方式编译效率会非常低下,甚至出错,于是我们可以把一些有关联的模块打包成一个文件来使用,即库文件。下面举例说明:
假设某工程目录如下:
# include目录,存放.h头文件,假设有class1.h,class2.h
# src目录,存放.cpp源文件,假设有class1.cpp,class2.cpp
# test目录,存放主程序,假设有main.cpp
# 1.先生成.o再链接
g++ -c src/class1.cpp src/class2.cpp test/main.cpp -I include/
g++ src/class1.o src/class2.o test/main.o -I include/ -o main
# 2.直接编译
g++ src/class1.cpp src/class2.cpp test/main.cpp -I include/ -o main
-I 用于指明头文件的路径,不加会提示找不到头文件。
其实,生成库文件是更常用的方式,这里讲一下静态库动态库的生成。
静态库是指编译链接时,就已经把库文件的实现代码加入到可执行文件中,生成的可执行文件体积较大,但是好处就是在运行时就不需要再依赖库文件。
在linux下,静态库命名一般为libxxx.a,其中xxx表示该库的名称。
# 1.编译生成.o文件
g++ -c class1.cpp class2.cpp -I include
# 2.使用ar命令创建.a动态库文件
ar crv libmyclass.a class1.o class2.o -I include
# 使用静态库(链接)
g++ test/main.cpp -lmyclass -L. -static -o main -I include # 或者
g++ test/main.cpp libmyclass.a -o main -I include
# 可以使用该命令查看.a文件内容
nm -s libmyclass.a
动态库与静态库相反,在编译链接时没有直接加入到可执行文件,而是在运行时链接文件加载库,可以节省系统开销,在项目中也方便版本迭代。
在linux下,动态库的名字一般为libxxx.so,其中xxx表示该库的名称。
# 编译动态库
g++ class1.cpp class2.cpp -fPIC -shared -o libmypen.so -I include
# 使用动态库(链接)
g++ test/main.cpp libmypen.so -I include -o main # 直接使用.so的绝对路径
g++ test/main.cpp -L. -lmypen -I include -o main
# 测试可执行是否正确链接到库
ldd main
-shared:该选项指定生成的库为动态链接库。
-fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
动态链接后的程序,在运行时需要配合动态库一起使用,如果找不到动态库,会报错。