ToB企服应用市场:ToB评测及商务社交产业平台

标题: 静态链接——编译和链接 [打印本页]

作者: 道家人    时间: 2023-7-7 17:13
标题: 静态链接——编译和链接
一、编译和链接的过程

1、GCC生成可执行文件的总体过程
  1. 在日常的开发过程中,IDE总是会帮我们将编译和链接合并,一键式的执行,即使在liunx中,使用命令行来编译一个源文件也只是简单的一句"gcc hello.c"。我们并没有过多的关注编译和链接的运行机制和机理,我想从本质出发,深入了解这些机制。对于下面一段hello.c代码
复制代码
  1. #include <stdio.h>
  2. int main()
  3. {
  4.   printf("Hello World\n");
  5.   return 0;
  6. }
复制代码
  1. 在liunx中,当我们用GCC来编译时只需要`gcc hello.c`即可生成`a.out`文件(并不是所有可执行文件都是`.out`),使用`./a.out`即可运行输出。实际上,上述的过程可以分解为四个步骤,分别是预处理(Prepressing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。
复制代码
  1.                                                                             GCC编译过程分解
复制代码
1.1、预编译(Prepressing)
  1. 预编译是指将源代码文件`(hello.c)`和相关头文件`(stdio.h)`等被**预编译器cpp**预编译成一个`.i`文件。需要注意的是对于C++程序来说,它的源代码文件的扩展名可能是`.cpp或.cxx`,头文件的扩展名可能是`.hpp`,而预编译后的文件扩展名是`.ii`。第一步预编译的过程相当于如下命令(E表示只进行预编译):` gcc -E hello.c -o hello.i 或者 cpp hello.c > hello.i`
  2. 预编译过程主要处理那些源代码文件中的以“#”开始的预编译指令。比如“#include”、“#define”等,主要处理规则如下:
复制代码
  1. # 1 "hello.c"
  2. # 1 "<built-in>"
  3. # 1 "<command-line>"
  4. # 1 "/usr/include/stdc-predef.h" 1 3 4
  5. # 1 "<command-line>" 2
  6. # 1 "hello.c"
  7. # 1 "/usr/include/stdio.h" 1 3 4
  8. # 27 "/usr/include/stdio.h" 3 4
  9. # 1 "/usr/include/features.h" 1 3 4
  10. # 375 "/usr/include/features.h" 3 4
  11. # 1 "/usr/include/sys/cdefs.h" 1 3 4
  12. # 392 "/usr/include/sys/cdefs.h" 3 4
  13. # 1 "/usr/include/bits/wordsize.h" 1 3 4
  14. # 393 "/usr/include/sys/cdefs.h" 2 3 4
  15. # 376 "/usr/include/features.h" 2 3 4
  16. # 399 "/usr/include/features.h" 3 4
  17. # 1 "/usr/include/gnu/stubs.h" 1 3 4
  18. # 10 "/usr/include/gnu/stubs.h" 3 4
  19. # 1 "/usr/include/gnu/stubs-64.h" 1 3 4
  20. # 11 "/usr/include/gnu/stubs.h" 2 3 4
  21. # 400 "/usr/include/features.h" 2 3 4
  22. # 28 "/usr/include/stdio.h" 2 3 4
  23. 部分展示
复制代码
经过预编译后的.i文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件也已经被插入到.i文件中。所以当我们无法判断宏定义是否正确或头文件包含是否正确时,可以查看预编译后的文件来确定问题。
1.2、编译(Compilation)
  1. 编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生产相应的汇编代码文件,这个过程往往是我们所说的整个程序构建的核心部分,也是最复杂的部分之一。上面的编译过程相当于如下命令:
复制代码
gcc -S hello.i -o hello.s
  1. 现在版本的GCC把预编译和编译两个步骤合并成一个步骤,使用一个叫做cc1的程序来完成这两个步骤。这个程序位于`usr/lib/gcc/i486-linux-gnu/4.1/`,我们也可以直接调用 ccl来完成它:
复制代码
usr/lib/gcc/i486-linux-gnu/4.1/cc1 hello.c或者gcc -S hello.c -o hello.s都可以得到汇编输出文件helIo.s。对于C语言的代码来说,这个预编译和编译的程序是ccI,对于C++来说,有对应的程序叫做cclplus:Objective-C是cclobj::fortran是f77l;Java是 jc1。所以实际上gcc这个命令只是这些后台程序的包装,它会根据不同的参数要求去调用预编译编译程序cc1、汇编器as、链接器ld。
  1.         .file        "hello.c"
  2.         .section        .rodata
  3. .LC0:
  4.         .string        "hello world"
  5.         .text
  6.         .globl        main
  7.         .type        main, @function
  8. main:
  9. .LFB0:
  10.         .cfi_startproc
  11.         pushq        %rbp
  12.         .cfi_def_cfa_offset 16
  13.         .cfi_offset 6, -16
  14.         movq        %rsp, %rbp
  15.         .cfi_def_cfa_register 6
  16.         movl        $.LC0, %edi
  17.         movl        $0, %eax
  18.         call        printf
  19.         movl        $0, %eax
  20.         popq        %rbp
  21.         .cfi_def_cfa 7, 8
  22.         ret
  23.         .cfi_endproc
  24. .LFE0:
  25.         .size        main, .-main
  26.         .ident        "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)"
  27.         .section        .note.GNU-stack,"",@progbits
  28. 展示hello.s中的内容
复制代码
1.3 汇编
  1. 汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。所以汇编器的汇编过程相对于编译器来讲比较简单,它没有复杂的语法,也没有语义,也不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译就可以了,“汇编”这个名字也来源于此。上面的汇编过程我]可以调用汇编器as来完成:
复制代码
as hello.s -o hello.o或者gcc -c hello.s -o hello.o或者使用gcc命令从C源代码文件开始,经过预编译、编译和汇编直接输出目标文件(Object File):gcc -c hello.c -o hello.o
1.4 链接
  1. 链接通常是一个让人比较费解的过程,为什么汇编器不直接输出可执行文件而是输出一个目标文件呢?链接过程到底包含了什么内容?为什么要链接?这恐怕是很多读者心中的疑惑。正是因为这些疑惑总是挥之不去,所以我们特意用这一章的篇幅来分析链接,具体地说分析静态链接的章节。下面让我们来看看怎么样调用ld才可以产生一个能够正常运行的 HelloWorld程序:
复制代码
  1. $ld -static /usr/lib/crt1.o /usr/lib/crti.o /uar/lib/gcc/1486-linux-gnu/4.1.3/crtbeginT.o -L/usr/lib/gcc/1486-linux-gnu/4.1.3 -L/usr/lib -L/lib hello.o --start-group-lgcc -lgcc_eh -1c --end-group /uar/lib/gcc/1486-linux-gnu/4.1.3/crtend.o /usr/lib/crtn.o
复制代码
如果把所有的路径都省略掉,那么上面的命令就是:
  1. ld -static crt1.o crti.o crtbeginT.o hello.o -start-group -lgcc -lgcc_eh -lc-end-group crtend.o crtn.o
复制代码
  1.         可以看到,我们需要将一大堆文件链接起来才可以得到“a.out”,即最终的可执行文件。看了这行复杂的命令,可能很多读者的疑惑更多了,ctl.o、crti.o、crtbegin T.o、crtend.o、 crtn.o这些文件是什么?它们做什么用的?-lgcc-lgcc_ehlc这些都是什么参数?为什么要使用它们?为什么要将它们和hello.o链接起来才可以得到可执行文件?等等。后面我们会陆续讲解
复制代码
参考:《程序员的自我修养》俞甲子

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4