1.编译流程
https://blog.csdn.net/williamgavin/article/details/83867408 参考的详细过程
gcc -E main.c -o main.i 预编译:将源代码main.c处理生成main.i。主要处理#注释或者宏定义等。
gcc -S main.i -o main.s 编译成汇编语言:将main.i编译成汇编语言
gcc -c main.s -o main.o 汇编语言编译成可重定位目标文件
gcc main.o -o main.exe 链接:将目标文件链接成可执行文件
2.预编译
主要是编译前处理。
3.编译成汇编语言
https://www.jianshu.com/p/e7a22923867f 函数栈帧的详细解释过程参考
下面是Math.cpp。
int t(int a, int b){
return a + b;
}
int add(int a, int b){
int c = 10;
int d = 15;
c = t(a, b);
return c + d;
}
下面是它编译后的汇编语言
.file "Math.cpp"
.text
.globl _Z1tii
.def _Z1tii; .scl 2; .type 32; .endef
.seh_proc _Z1tii
_Z1tii:
.LFB0:
pushq %rbp
.seh_pushreg %rbp
movq %rsp, %rbp
.seh_setframe %rbp, 0
.seh_endprologue
movl %ecx, 16(%rbp)
movl %edx, 24(%rbp)
movl 16(%rbp), %edx
movl 24(%rbp), %eax
addl %edx, %eax
popq %rbp
ret
.seh_endproc
.globl _Z3addii
.def _Z3addii; .scl 2; .type 32; .endef
.seh_proc _Z3addii
_Z3addii: //函数的名字是add 后面两个i表示两个参数,三个参数的函数会产生三个i
.LFB1:
pushq %rbp //将调用这个函数的函数的栈帧的底部栈指针存进本函数栈帧。用于恢复调用本函数的函数。
.seh_pushreg %rbp
movq %rsp, %rbp //将栈顶指针数据赋值给栈底指针。这时进入到了新的函数栈帧。
.seh_setframe %rbp, 0
subq $48, %rsp
.seh_stackalloc 48
.seh_endprologue
movl %ecx, 16(%rbp) //保存参数。本函数总共有2个参数。调用本函数的函数使用的参数数据在计数寄存器中。将数寄 //存器中的数据存入上一个函数(也是就调用本函数的函数)的栈帧中。地址是栈底数据的上 //16字节。也就是参数还是保存在调用函数的栈帧中的。
movl %edx, 24(%rbp) //同理保存第二个参数。保存在地址是栈底数据的上24字节。
movl $10, -4(%rbp) // 这里给int c 开辟栈空间 并赋值10
movl $15, -8(%rbp) // 这里给int d 开辟栈空间 并赋值15
movl 24(%rbp), %eax //累加器放入参数b
movl %eax, %edx //数据寄存器放入参数b
movl 16(%rbp), %ecx //计数寄存器放入参数a
call _Z1tii //调用t函数
movl %eax, -4(%rbp) //将t的返回值给c。这里经过t函数,累加器里的值变为了返回值
movl -4(%rbp), %edx //下面都是相加的过程了
movl -8(%rbp), %eax
addl %edx, %eax //将两个寄存器中的数值相加,结果放入累加器寄存器中。返回值存放在累加寄存器中。
addq $48, %rsp
popq %rbp //弹出数据,放入基址寄存器中。也就是本栈帧中存储的第一个数据:调用函数的栈底指针函数,用 //来返回,调用函数。
ret
.seh_endproc
.ident "GCC: (tdm64-1) 4.9.2"
每调用一次函数,就会增加一个函数栈帧,并移动到新的栈帧。给栈帧分配的空间由函数里使用的变量个数和大小决定。
int& a = add(5, 6); //这里的使用方法是错误的。因为这时返回值在累加器寄存器中。没有地址所以不能用引用。
这时可以加上const const int& a = add(5, 6); 这时候将引用变为静态变量,在栈中给其分配静态空间。这时a值不再能更改。这里相当于赋值,并不再是引用。相当于 const int a。
4.生成可重定位目标文件
可重定位目标文件格式详细解释参考:https://blog.csdn.net/Xindolia_Ring/article/details/80961363
编译器会将每个cpp文件编译成.o文件。在Windows中是.obj文件,但是编译生成的文件后缀还是.o.在Linux中是.o文件。
Windows可重定位目标文件格式是PE格式, Linux可重定位目标文件格式是ELF格式。两种格式都是从coff格式发展而来。
windows 利用objdump 可以查看PE格式文件内容
查看Math.o 可重定位目标文件 发现他的格式是 pe-x86-64
0.text 是代码段 这里存储二进制代码。已经编译成机器代码。
1.data 存放的是已经初始化的全局变量和已经初始化了的static变量
2.bss 存放的是未初始化的全局变量和未初始化的static变量
3.stack heap 栈区和堆区 栈里面存放函数内部变量,前面汇编部分已经很清楚了。堆是动态存储区,前面几个都是静态存储区。动态意思是存储空间是不固定的随着程序的执行会增加和缩小。静态是从程序开始存储区就是固定了的。凡是 new 或者 malloc 开辟的空间都是在堆里的。
4. xdata pdata data 都是存放已经初始化的全局变量和static变量的区域。 不同的是存储的物理空间的不同。data 是可寻址片内ram xdata是可寻址片外ram pdata 是分页寻址片外ram idata是可寻址片内ram,允许访问全部内部ram。
5.链接
将可重定位目标文件链接为一个整体。
每个cpp生成一个.o文件。但是 .o文件之间可能互相调用。对于本文件里已经声明但是没有定义的函数,不确定外部文件有没有定义此函数。所以.o文件将函数地址空出。等链接的时候,将各个有调用关系的.o文件相互串联起来。也就是将汇编中的call 地址填上。
6.静态库文件,动态库文件,可执行文件
这三者都是和.o文件格式相同,PE格式或者ELF格式。
lib文件是.o文件的集合。实际上和exe可执行文件差不多,只不过没有main函数。他的生成也是由众多.o文件链接生成的。如果只有单个.o文件。那.o文件和生成的.lib文件是一样的。因为根本就不用链接。所以devc++ 添加外部静态链接的时候可以添加.a也可以添加.o。这里的.a就相当于.lib。
例如用objdump 查看exe文件: