生成一个可执行文件。包括两部分:编译阶段,链接阶段。
- 编译阶段包括三个步骤。
预处理,编译,汇编 ----> 生成obj文件;
- 链接阶段包括两个步骤。
对obj文件的合并,符号表合并后,对符号进行解析,分配虚拟地址;
对符号进行重定位。
编译阶段的编译最耗时间。是检查语法等功能,生成汇编代码。
1. g++编译过程
g++在对源程序执行编译工作时,需要经过以下四个步骤:(其中 -o 可以理解为将生成的文件 重命名的作用)
(1) 预处理:对源程序中的宏定义、包含头文件等进行处理,生成后缀名为.i的文件(使用)
g++ -E hello.cpp -o hello.i
hello.cpp是需要编译的源文件,-o 选项指定输出的文件名。这里使用-E选项编译生成hello.i 文件
(2) 转化为汇编文件:使用-S 选项,可以将预处理之后的.i文件转换为目标机器的汇编代码.S文件
g++ -S hello.i -o hello.s
使用该参数可以将.i 文件编译生成.s 文件,输出文件名同样使用-o 选项指定。
(3) 汇编文件->目标文件,即转换为机器代码:使用-c选项
g++ -c hello.s -o hello.o
(4) 链接:将上一步产生的目标文件链接为可执行文件,使用-o参数
静态链接 ld -e main main.o main 为入口函数,.o文件为需要链接的obj文件。
以上过程是g++工具编译cpp源程序的具体过程,在实际使用时我们可以不用按照流程一步步编译,可以一步到位将源程序编译为可执行文件,只需要使用如下命令:
g++ hello.cpp -o hello
2、编译结果与链接结果
以这两个函数为例
sum.cpp
int c = 10;
int sum(int a, int b)
{
int temp = 0;
temp = a + b;
return temp;
}
main.cpp
extern int c;
int sum(init a, int b);
int main()
{
int a = 10;
int b = 20;
int ret = sum(a, b);
return 0;
}- 生成可重定位文件。
g++ -c main.cpp g++ -c sum.cpp 分别生成了 main.o sum.o,
利用objdump -t main.o 输出目标文件的符号表。在下面,可以看到符号表中有 哪些段。其中有 *UND* 是 sum 文件中的函数和全局数据。从而得知,符号表是在编译阶段后就有了。
main.o: file format elf64-x86-64
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 main.cpp
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g F .text 000000000000002f main
0000000000000000 *UND* 0000000000000000 _GLOBAL_OFFSET_TABLE_
0000000000000000 *UND* 0000000000000000 _Z3sumii
利用 objdump -S main.o 可以查看 main.o 反汇编的结果。并没有给其他obj文件 符号分配地址。可以看 call sum,其地址为 00 00 00 00 如果想一句一句看汇编的话,则 g++ -c main.cpp -g。再objdump -S main.o
main.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: c7 45 f4 0a 00 00 00 movl $0xa,-0xc(%rbp)
f: c7 45 f8 14 00 00 00 movl $0x14,-0x8(%rbp)
16: 8b 55 f8 mov -0x8(%rbp),%edx
19: 8b 45 f4 mov -0xc(%rbp),%eax
1c: 89 d6 mov %edx,%esi
1e: 89 c7 mov %eax,%edi
20: e8 00 00 00 00 callq 25 <main+0x25> call sum函数,sum函数的地址为0
25: 89 45 fc mov %eax,-0x4(%rbp)
28: b8 00 00 00 00 mov $0x0,%eax
2d: c9 leaveq
2e: c3 retq
- 静态链接
ld -e main *.o 生成 a.out objdump -S a.out 可以看到符号已经重定位。
a.out: file format elf64-x86-64
Disassembly of section .text:
00000000004000e8 <main>:
4000e8: 55 push %rbp
4000e9: 48 89 e5 mov %rsp,%rbp
4000ec: 48 83 ec 10 sub $0x10,%rsp
4000f0: c7 45 f4 0a 00 00 00 movl $0xa,-0xc(%rbp)
4000f7: c7 45 f8 14 00 00 00 movl $0x14,-0x8(%rbp)
4000fe: 8b 55 f8 mov -0x8(%rbp),%edx
400101: 8b 45 f4 mov -0xc(%rbp),%eax
400104: 89 d6 mov %edx,%esi
400106: 89 c7 mov %eax,%edi
400108: e8 0a 00 00 00 callq 400117 <_Z3sumii>
40010d: 89 45 fc mov %eax,-0x4(%rbp)
400110: b8 00 00 00 00 mov $0x0,%eax
400115: c9 leaveq
400116: c3 retq
0000000000400117 <_Z3sumii>:
400117: 55 push %rbp
400118: 48 89 e5 mov %rsp,%rbp
40011b: 89 7d ec mov %edi,-0x14(%rbp)
还可以利用 readelf 来查看 obj文件的信息。
3.g++常用编译选项
在使用g++工具进行编译时,我们可以附加一些编译选项让编译更加智能,从而方便我们查看编译错误和警告。g++提供了许多有用的编译选项,下面总结一些常用选项:
-o FILE: 指定输出文件名,在编译为目标代码时,这一选项不是必须的。如果FILE没有指定,缺省文件名是a.out
-Wall: 允许发出gcc能提供的所有有用的警告,也可以用-W(warning)来标记指定的警告
-v: 显示在编译过程的每一步中用到的命令 ;
-g: 在可执行程序中包含标准调试信息, 使用该选项生成的可执行文件可以用gdb工具进行调试;
-L: 库文件依赖选项,该选项用于指定编译的源程序依赖的库文件路径,库文件可以是静态链接库,也可以是动态链接库,linux系统默认的库路径是/usr/lib,如果需要的库文件不在这个路径下就要用-L指定
g++ foo.cpp -L/home/lib -lfoo -o foo
-I: 该选项用于指定编译程序时依赖的头文件路径,linux平台默认头文件路径在/usr/include下,如果不在该目录下,则编译时需要使用该选项指定头文件所在路径
gcc foo.cpp -I/home/include -o foo
4.编译动态库
g++除了可以编译源程序生成可执行文件,也可以编译动态链接库,方法如下:
(1) 分步完成
gcc -fPIC -c func.cpp -o func.o
gcc -shared -o libfunc.so func.o
(2) 一步完成
gcc -fPIC -shared -o libfunc.so func.cpp
5. make和Makefile
已经介绍了g++工具在命令行的使用,我们可以使用g++在命令行来对单个或者多个源程序文件进行编译。但是,如果遇到需要同时编译许多文件的情况,g++命令行编译就太麻烦了,这个时候就需要使用make进行自动化编译。我们可以编写make工具的规则文件——Makefile,在该文件中定义一些编译的规则,然后利用make工具自动调用g++进行批量编译,这样可以大大提高效率。
6、camke 和 CMakeLists.txt
cmake 与 CMakeLists.txt ,则使更加简单,且跨平台。书写CMakeLists.txt 之后,执行cmake,生成 Makefile 文件,再make。