• [学习笔记]《CSAPP》深入理解计算机系统 - Chapter 7 链接


    Chapter 7 链接

    1. 链接由"链接器(separate compilation)"的程序自动执行的

    编译器驱动程序

    // main.c
    int sum(int *a, int *n);
    
    int array[2] = {1, 2};
    
    int main()
    {
        int val = sum(array, 2);
        return val;
    }
    
    // sum.c
    int sum(int *a, int n)
    {
        int i, s = 0;
        for (i = 0; i < n; i++) {
            s += a[i];
        }
        return s;
    }
    
    1. 驱动程序首先运行C预处理器(cpp),将源程序翻译成一个ASCII码的中间文件main.i
    2. 然后驱动器运行C编译器(ccl),将main.i翻译成一个ASCII汇编语言文件main.s
    3. 然后运行汇编器as,将main.s翻译成一个“可重定位目标文件(relocatable object file)main.o
    4. 通过相同的过程生成sum.o
    5. 最后运行链接器程序ld,将main.osum.o以及一些必要的系统目标文件组合起来,创建一个可执行目标文件executable object file prog
      ld -o prog [system object files and args] main.o sum.o

    目标文件

    • 可重定位目标文件: 包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件
    • 可执行目标文件: 包含二进制代码和数据,其形式可以被直接复制到内存并执行
    • 共享目标文件: 一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态地加载进内存并链接

    reference to Page 467 to understand重定位目标文件里的内存内容,比如ELF.text, .rodata, .data, .bss, .symtab, .rel.text, .rel.data, .debug等内容

    ps: 初始化的全局和静态C变量存在 .data里。

    局部 C 变量在运行时被保存在栈中;

    未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量保存在 .bss中,且不占据实际空间,仅仅是一个占位符。

    区分初始化和非初始化是为了空间效率: 在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。运行时,在内存中分配这些变量,初始值为0

    .debug: 一个调试符号表,只有以 -g 选项调用编译器驱动程序时,才会得到这个表

    1. C++ and JAVA都允许重载方法,这些方法在源代码中有相同的名字,却有不通的参数列表。这是编译器将每个唯一的方法和参数列表组成编码成一个对链接器来说唯一的名字。

    与静态库链接

    相关函数被编译为独立的目标模块,然后封装成一个单独的静态库文件。
    linux> gcc main.c xxx.a bbb.a,在链接时,链接器将只赋值被程序引用的目标模块(函数),这就减少了可执行文件在磁盘和内存中的大小。

    编译静态库示例

    linux> gcc -c func1.c func2.c
    linux> ar rcs libfunc.a func1.o func2.o
    // 编写一个头文件func.h,里面时对两个.c文件中函数的声明, 并在 main.c 中包含这个头文件
    linux> gcc -c main.c
    linux> gcc -static -o prog main.o -L. -lfunc
    or
    linux> gcc -static -o prog main.o ./libfunc.a
    

    -static 高速编译器驱动程序,链接器应该构建一个完全链接的可执行目标文件, 它可以加载到内存并运行,在加载时无需更进一步的链接
    -lfunc参数是libfunc.a的缩写
    -L. 参数高速链接器在当前目录下(.)查找libfunc.a

    reference to page 477
    链接器的主要任务是将程序的不同部分(如编译后的代码模块、库文件、目标文件等)合并成一个单一的可执行文件或库文件。这个过程涉及到几个关键步骤,包括解析头文件中的引用和库文件或目标文件中的定义。以下是链接器如何工作的详细说明:
    
    解析符号引用:
    
    头文件中的声明(如函数原型和变量声明)在编译时被转换为符号引用,这些引用在目标文件中被保留。
    

    如果定义一个符号的库,出现在引用这个符号的目标文件之前,引用就不能被解析,链接会失败

    动态链接共享库

    是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。这个过程称为动态链接,由一个叫动态链接器的程序执行。

    一个库只有一个.so文件。所有引用该库的可执行目标文件共享这个 .so文件中的代码和数据,而不是像静态库的内容那样被复制和嵌入到引用他们的可执行文件中。其次,在内存中,一个共享库的.text节的一个副本,可以被不通的正在运行的进程共享。(reference chatper 9)

    linux> gcc -shared -fpic -o libfunc.so func1.c func2.c
    -fpic指示编译器生成与位置无关的代码
    -shared指示链接器创建一个共享的目标文件。
    linux> gcc -o prog main.c ./libfunc.so

    加载可执行目标文件

    每一个Linux程序都有一个运行时内存影响。从地址 2 48 2^{48} 248开始(示例),是为内存中的代码和数据保留的,也就是所谓的内核。

  • 相关阅读:
    进阶JAVA篇- Collcetions 工具类与集合的并发修改异常问题
    资深架构师必备知识:Netty+MySQL+并发+JVM+多线程
    鸿蒙开发接口媒体:【@ohos.multimedia.audio (音频管理)】
    kafka实战报错解决问题
    学废Elasticsearch(一)
    Nginx Rewrite
    m=m++到底发生了什么
    Vue vs React:你需要知道的一切
    DevOps --- Pipeline和Yaml文件
    为什么说区块链的性能难以衡量?
  • 原文地址:https://blog.csdn.net/qq_39274501/article/details/142102525