0%

程序员自我修养读后感(2)-静态链接

静态链接1

编译器的工作

完整的程序编译包含以下流程

1
2
预编译 - 编译 - 汇编 - 链接

预编译

主要就是一些宏展开, 预处理条件处理、删除注释、保存文件名及行号

1
2
gcc -E file.c -o file.i

生成的file.i也是一个可读的文本文件, 具体可以查看里面的内容

编译

对代码进行词法分析、语法分析、语义分析以及优化后产生对应的汇编代码

1
2
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
2
Keep:programmer-up keep$ file a // mac下的显示, linux也差不多吧
a: Mach-O 64-bit object x86_64

mac的可执行文件与linux还是有差别的, mac下是mach-o的格式(苹果自家的格式), 这里不细讲了。

文件头

目标文件中包含机器码、数据、调试符号信息、字符串等信息,文件按信息的属性存放,分为不同的Section, 每个section都是一个定长的区域,可以通过一个段表的section查询各个段的信息, 段表的信息又可以在一个成为文件头的地方查找到。文件头是在文件开始区域的一段固定长度的区域,里面描述了文件的基本信息。

文件头的信息可以通过 命令查看

1
2
readelf -h filename

通过objdump命令可以查看文件包含的各个段

1
2
3
4
5
6
7
8
9
10
Keep:programmer-up keep$ objdump -h a

a: file format Mach-O 64-bit x86-64

Sections:
Idx Name Size Address Type
0 __text 00000035 0000000000000000 TEXT
1 __compact_unwind 00000020 0000000000000038 DATA
2 __eh_frame 00000040 0000000000000058 DATA

由于我是在mac电脑上查看的, 会和在linux上显示的不一样,不过基本的信息是一样的

使用size可以查看到代码段、bss段、数据段的大小。

可以通过objdump、readelf查看文件的内容,具体的请查看命令行对应的选项。

主要的段:

  • 代码段:存放代码指令的
  • 数据段:存放初始化后的全局变量、静态变量
  • 只读数据段:存放只读变量、字符串常量
  • bss段: 存放未初始化的全局变量和局部静态变量
  • 字符串表: 存储段名称、变量名称、函数名称的字符串表
  • 符号表: 符号与地址的信息映射表

还可以自定义段,把变量或者函数放到指定的段中

1
2
int a __attribute__((section("custom")));

其它

extern “C”

由于符号的不一致性, 但又希望C可以被C++代码引用,一般把C语言的代码通过 extern “C”包含起来,这样编译处理的符号还是C语言格式的。

1
2
3
4
5
6
7
8
9
#ifdef __cplusplus
extern "C" int func(int);

extern "C" {
#endif
// c statement
#ifdef __cplusplus
};
#endif

强符号与弱符号

__attribute__(weak)

  • 不允许出现多个同名的强符号定义
  • 既存在强符号、又存在弱符号, 链接时选择强符号
  • 所有定义都是弱符号, 则选择占用空间最大的那个符号

可以用来做插件,内容声明为弱符号, 外部声明为强符号,当外部实现了功能,则使用外部的功能。

之前的博客写过遇到过一个关于弱符号的问题 点击查看

调试信息

编译时,添加 -g选项,则会往目标文件写入调试的信息。ELF采用一种DWARF的调试信息格式。一般调试信息会占用挺大的空间的,对空间敏感的场景下可以通过strip删掉目标文件中调试相关的段,减小文件的大小。这样的目标文件不会影响代码的正常运行,但是不好进行gdb调试了。

1
strip filename

行动,才不会被动!

欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。