在做一个测试博客的时候发现自己对使用gcc/g++ 查看汇编代码以及C/C++的汇编等阶段还有一点模糊,特此记录一下。
源代码编译过程
1.预处理
C/C++的预处理其实就是一个词法(而不是语法)预处理器,其主要完成文本替换、宏展开以及删除注释等,完成这些操作之后,将会获得真正地“源代码”。
常见的include语句即是一个预处理器命名,在预处理器中它将所有的头文件包含进来。(该步骤的文件扩展名为.i)
2.编译
在这一步骤,将.i文件翻译为.s,得到汇编程序语言,值得注意的是所有的编译器输出的汇编语言都是同一种语法。
注:内联函数就是在这一环节“膨胀”进源码的,它的作用即在于:不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处,适用于功能简单,规模较小又使用频繁的函数。递归函数无法内联处理,内联函数不能有循环体,switch语句,不能进行异常接口声明。**仅仅省去了函数调用的开销,从而提高函数的执行效率。**如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
3.汇编
将.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件.o中(把汇编语言翻译成机器语言的过程)。
4.链接
链接(ld):gcc会到系统默认的搜索路径”/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去。
函数库一般分为静态库和动态库两种。静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为”.a”。动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统存储的开销。动态库一般后缀名为”.so”,如前面所述的libc.so.6就是动态库。gcc在编译时默认使用动态库。
查看汇编
复习了基础知识,用实例来介绍如何使用gcc/g++查看各阶段代码(限于篇幅只介绍汇编):
建立一个源文件如下:
#include <stdio.h>
int main(){
int a=1;
int b=2;
int c=a+b;
return c;
}
使用指令编译成汇编代码:
cat/gedit/vim等方式查看汇编代码:
.file "test_gcc.c"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $1, -12(%rbp)
movl $2, -8(%rbp)
movl -12(%rbp), %edx
movl -8(%rbp), %eax
addl %edx, %eax
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
根据命令选项的不同,可以生成各个阶段的文件,详细指令如下:
| 参数 | 说明 |
|---|---|
| -c | 只编译不链接,生成*.o文件 |
| -S | 生成汇编代码*.s文件 |
| -E | 预编译 生成*.i文件 |
| -g | 在可执行程序里包含了调试信息,可用 gdb 调试 |
| -o | 把输出文件输出到指定文件里 |
| -static | 链接静态链接库 |
| -library | 链接名为library的链接库 |
本人博客均为原创,不定时更新(2021/4/22),转载请注明链接:juejin.cn/post/695378…
创作不易,如果你觉得博客写的还不错,请点个赞或者留个言。
希望在以后的日子里我们能够一起探寻计算机的!
——来自一个默默积累总结的准码农
参考资料: