刚刚看完了CSAPP的linking
一章,试着简单回顾一下。
C语言允许将工程分为多个文件单独管理和编译,这对模块化编程是很好地支持。Linking
则将多个代码与数据片断连接起来成为一个完整的可很执行程序。
需要注意多个文件中定义同名(数据类型可同可不同)全局变量是允许的,只要它们最多只有一个进行了初始化,这其实是一个很容易导致运行时才发现错误的特性,所以有时候可以使用gcc
参数-warn-common
来提示这些同名的全局变量。
compiler driver
假设一个工程包含两个文件swap.c
和main.c
,我们可以将它们一起编译为一个可执行文件
1
| gcc main.c swap.c -o myapp
|
这其中其它包含了预处理、汇编、编译和链接的过程,我们可以将它们分开执行。
如果包含了标准库,一般会生成一个很大的文本文件。
1 2 3 4 5 6 7 8 9
| 835 extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)); 836 # 943 "/usr/include/stdio.h" 3 4 837 838 # 3 "main.c" 2 839 840 void swap(); 841 842 int buf[2] = {1, 2}; 843 int main(){
|
可以将此预处理后的文件转换为汇编文件。
这将自动生成一个名为main.s的文本文件
1 2 3 4 5 6
| movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp movl $1, -8(%rbp) movl $3, -4(%rbp) leaq -4(%rbp), %rdx
|
可以将其编译为目标文件
这已经是一个二进制文件,可以使用readelf
,objdump
查看它的相关信息.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| ##反编译汇编代码 p$ objdump -d 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 f8 01 00 00 00 movl $0x1,-0x8(%rbp) f: c7 45 fc 03 00 00 00 movl $0x3,-0x4(%rbp) 16: 48 8d 55 fc lea -0x4(%rbp),%rdx 1a: 48 8d 45 f8 lea -0x8(%rbp),%rax 1e: 48 89 d6 mov %rdx,%rsi 21: 48 89 c7 mov %rax,%rdi 24: b8 00 00 00 00 mov $0x0,%eax 29: e8 00 00 00 00 callq 2e <main+0x2e> 2e: 8b 55 fc mov -0x4(%rbp),%edx 31: 8b 45 f8 mov -0x8(%rbp),%eax 34: 89 c6 mov %eax,%esi 36: bf 00 00 00 00 mov $0x0,%edi 3b: b8 00 00 00 00 mov $0x0,%eax 40: e8 00 00 00 00 callq 45 <main+0x45> 45: c9 leaveq 46: c3 retq ##查看elf格式相关段,以下为symtab段信息 $ readelf -s main.o Symbol table '.symtab' contains 12 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 3: 0000000000000000 0 SECTION LOCAL DEFAULT 3 4: 0000000000000000 0 SECTION LOCAL DEFAULT 4 5: 0000000000000000 0 SECTION LOCAL DEFAULT 5 6: 0000000000000000 0 SECTION LOCAL DEFAULT 7 7: 0000000000000000 0 SECTION LOCAL DEFAULT 8 8: 0000000000000000 0 SECTION LOCAL DEFAULT 6 9: 0000000000000000 71 FUNC GLOBAL DEFAULT 1 main 10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND swap 11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
|
使用ld可以将多个目标文件链接成一个可执行文件
库函数
C语言中将许多常用的函数的目标文件用archive格式进行组织,形成一个个函数库,方便开发。例如默认包含的libc.a
(其中包含printf
等函数的目标文件),用于数学计算的libm.a
等。
我们也可以生成自己的函数库。
1 2
| ##将两个relocatable目标文件打包成库文件archive ar rcs libvector.a addvec.o multvec.o
|
使用该库进行编译,static意为生成一个不依赖任何外部目标文件的可执行文件
1 2
| gcc -static main.c libvector.a ##一般将库放在后面,否则易出错,同一文件可重复出现在参数中
|
动态链接
动态链接可在运行时加载指定代码,多个进程可共享同一内存片段,具有相当多的优点。我们可以将自己所写方法生成动态函数库。
1
| $ gcc -fPIC -shared swap2.c -o libswap.so
|
使用动态库,需要用到dlfcn.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <stdio.h> #include <stdlib.h> #include <dlfcn.h> main(){ int buf[] = {3, 5}; void (*swap)(int*, int*); char* error; void* handle = dlopen("./libswap.so",RTLD_LAZY); if(!handle){ fprintf(stderr,"%s\n",dlerror()); exit(1); } swap = dlsym(handle, "swap"); if( (error = dlerror()) != NULL){ fprintf(stderr,"%s\n",dlerror()); exit(1); } swap(buf,buf+1); printf("buf = [%d, %d]\n",buf[0],buf[1]); if(0>dlclose(handle)){ fprintf(stderr,"%s\n",dlerror()); exit(1); } }
|
使用以下命令进行编译运行,注意-rdynamic
, main2.c
和-ldl
的位置关系。
1 2 3
| $gcc -rdynamic -O2 main2.c -ldl -o p $ ./p buf = [5, 3]
|
小结
CSAPP的这一节中其实重点在讲relocatable
目标文件的组织结构,以及如何从源文件生成及它又如何转换为executable
目标文件,但几乎每一句都是重要内容所以不方便摘录,推荐阅读原文。这里只是记录了一些实际操作相关。