静态链接1
编译器的工作
完整的程序编译包含以下流程
1 | 预编译 - 编译 - 汇编 - 链接 |
预编译
主要就是一些宏展开, 预处理条件处理、删除注释、保存文件名及行号
1 | gcc -E file.c -o file.i |
生成的file.i也是一个可读的文本文件, 具体可以查看里面的内容
编译
对代码进行词法分析、语法分析、语义分析以及优化后产生对应的汇编代码
1 | gcc -S file.i -o file.s |
file.s为编译后的汇编代码
汇编
这一步是把汇编代码转为机器指令
1 | gcc -c file.s -o a.o |
a.o为目标文件
链接
目标文件只有单个文件的内容, 单个文件可能引用外部文件的变量或者函数,通过链接把多个文件的内容整合起来,最终输出可执行的文件。
1 | ld -static a.o b.o -o a.out |
一个文件修改后,需要重新链接才能生效。
链接主要涉及了地址和空间的分配、符号决议和符号的重定位等流程。
目标文件的格式
要把多个文件链接成一个独立的文件,中间少不了约定统一的文件格式,才能通过解析文件进行处理。
Linux下的目标文件格式主要是ELF格式, 包含可执行文件、目标文件、静态库、动态库都是用的一样的格式。
我们可以通过file命令查看文件的类型
1 | Keep:programmer-up keep$ file a // mac下的显示, linux也差不多吧 |
mac的可执行文件与linux还是有差别的, mac下是mach-o的格式(苹果自家的格式), 这里不细讲了。
文件头
目标文件中包含机器码、数据、调试符号信息、字符串等信息,文件按信息的属性存放,分为不同的Section, 每个section都是一个定长的区域,可以通过一个段表的section查询各个段的信息, 段表的信息又可以在一个成为文件头的地方查找到。文件头是在文件开始区域的一段固定长度的区域,里面描述了文件的基本信息。
文件头的信息可以通过 命令查看
1 | readelf -h filename |
通过objdump命令可以查看文件包含的各个段
1 | Keep:programmer-up keep$ objdump -h a |
由于我是在mac电脑上查看的, 会和在linux上显示的不一样,不过基本的信息是一样的
使用size可以查看到代码段、bss段、数据段的大小。
段
可以通过objdump、readelf查看文件的内容,具体的请查看命令行对应的选项。
主要的段:
- 代码段:存放代码指令的
- 数据段:存放初始化后的全局变量、静态变量
- 只读数据段:存放只读变量、字符串常量
- bss段: 存放未初始化的全局变量和局部静态变量
- 字符串表: 存储段名称、变量名称、函数名称的字符串表
- 符号表: 符号与地址的信息映射表
还可以自定义段,把变量或者函数放到指定的段中
1 | int a __attribute__((section("custom"))); |
其它
extern “C”
由于符号的不一致性, 但又希望C可以被C++代码引用,一般把C语言的代码通过 extern “C”包含起来,这样编译处理的符号还是C语言格式的。
1 |
|
强符号与弱符号
__attribute__(weak)
- 不允许出现多个同名的强符号定义
- 既存在强符号、又存在弱符号, 链接时选择强符号
- 所有定义都是弱符号, 则选择占用空间最大的那个符号
可以用来做插件,内容声明为弱符号, 外部声明为强符号,当外部实现了功能,则使用外部的功能。
之前的博客写过遇到过一个关于弱符号的问题 点击查看
调试信息
编译时,添加 -g选项,则会往目标文件写入调试的信息。ELF采用一种DWARF的调试信息格式。一般调试信息会占用挺大的空间的,对空间敏感的场景下可以通过strip删掉目标文件中调试相关的段,减小文件的大小。这样的目标文件不会影响代码的正常运行,但是不好进行gdb调试了。
1 | strip filename |
行动,才不会被动!
欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。