计算机系统篇之链接(4):静态链接(中)——符号解析

Author: stormQ

Created: Wednesday, 15. April 2020 12:35PM

Last Modified: Monday, 18. January 2021 09:11PM



摘要

本文描述了 Linux 系统中符号解析的工作原理,并通过示例展示了符号解析过程可能引起的链接期错误和运行期错误。

符号解析的整体过程

符号解析的目的是将每个符号引用与唯一的符号定义关联起来。符号解析的过程由编译器、汇编器和链接器协作完成。具体地处理行为如下:

符号 编译器如何处理 汇编器如何处理 链接器如何处理
对于全局符号定义 如果同一个全局符号在同一个目标模块内有多个定义,那么编译器会报错,不会进行后面的汇编和链接过程。 只要编译器不报错,汇编器也不会报错。 如果同一个全局符号在不同的目标模块内都有定义,那么链接器会报错。
对于全局符号引用
  • 如果一个全局符号引用在目标模块内既没有声明也没有定义,那么编译器会报错,不会进行后面的汇编和链接过程。
  • 如果一个全局符号引用在目标模块内只有声明没有定义,那么编译器不会报错,继续进行后面的汇编和链接过程。
  • 只要编译器不报错,汇编器也不会报错。 如果一个全局符号引用在其他目标模块内都没有定义,那么链接器会报错。
    对于局部符号定义 如果同一个局部符号在同一个目标模块内有多个定义,那么编译器会报错,不会进行后面的汇编和链接过程。 只要编译器不报错,汇编器也不会报错。 链接器不会涉及局部符号定义的处理过程。
    对于局部符号引用
  • 如果一个局部符号引用在同一个目标模块内既没有声明也没有定义,那么编译器会报错,不会进行后面的汇编和链接过程。
  • 如果一个局部符号引用在同一个目标模块内只有声明但没有定义,那么编译器会报错,不会进行后面的汇编和链接过程。
  • 只要编译器不报错,汇编器也不会报错。 链接器不会涉及局部符号引用的处理过程。

    1)验证“如果同一个全局符号在同一个目标模块内有多个定义,那么编译器会报错,不会进行后面的汇编和链接过程。”

    # 全局变量 g_val(int 类型的)和 g_val(double 类型的)对应的全局符号的名称相同,属于“同一个全局符号在同一个目标模块内有多个定义”的情况
    # 函数 int func(int val) 和 void func(int val) 对应的全局符号的名称相同,属于“同一个全局符号在同一个目标模块内有多个定义”的情况
    $ cat compile_error1.cpp 
    int g_val = 0;
    double g_val = 0;

    int func(int val)
    {
      return val;
    }

    void func(int val)
    {
    }

    # 在生成汇编文件时报错,即编译器会报错
    $ g++ -S compile_error1.cpp -o compile_error1.s
    compile_error1.cpp:2:8: error: conflicting declaration ‘double g_val’
     double g_val;
            ^~~~~
    compile_error1.cpp:1:5: note: previous declaration as ‘int g_val’
     int g_val;
         ^~~~~
    compile_error1.cpp: In function ‘void func(int)’:
    compile_error1.cpp:9:6: error: ambiguating new declaration of ‘void func(int)’
     void func(int val)
          ^~~~
    compile_error1.cpp:4:5: note: old declaration ‘int func(int)’
     int func(int val)
         ^~~~

    2)验证“如果同一个全局符号在不同的目标模块内都有定义,那么链接器会报错。”

    # 在不同的目标模块中定义相同名称的全局符号
    $ cat link_error1.cpp 
    int g_val = 1;

    int func(int val)
    {
      return val;
    }
    $ cat main.cpp
    int g_val = 100;

    void func(int val)
    {
    }

    int main()
    {
        return 0;
    }
    # 编译器和汇编器不会报错
    $ g++ -c link_error1.cpp -o link_error1.o
    $ g++ -c main.cpp -o main.o
    # 在生成可执行目标文件时报错,即链接器会报错
    $ g++ -o main main.o link_error1.o
    link_error1.o:(.data+0x0): multiple definition of `g_val'
    main.o:(.data+0x0): first defined here
    link_error1.o: In function `
    func(int)':
    link_error1.cpp:(.text+0x0)multiple definition of `func(int)'
    main.o:main.cpp:(.text+0x0)first defined here
    collect2errorld returned 1 exit status

    3)验证“如果一个全局符号引用在目标模块内既没有声明也没有定义,那么编译器会报错,不会进行后面的汇编和链接过程。”

    # func2 在目标模块中既没有声明也没有定义
    $ cat compile_error2.cpp 
    int func(int val)
    {
      return val + func2(val);
    }
    # 编译器会报错
    $ g++ -S compile_error2.cpp -o compile_error2.s
    compile_error2.cpp: In function ‘int func(int)’:
    compile_error2.cpp:3:25: error: ‘func2’ was not declared in this scope
       return val + func2(val);
                             ^

    4)验证“如果一个全局符号引用在目标模块内只有声明没有定义,那么编译器不会报错,继续进行后面的汇编和链接过程。”

    # 一个全局符号引用在目标模块内只有声明没有定义
    $ cat compile_correct1.cpp 
    int func2(int)
    ;

    int func(int val)
    {
      return val + func2(val);
    }
    # 编译器和汇编器不会报错
    $ g++ -c compile_correct1.cpp -o compile_correct

    5)验证“如果一个全局符号引用在其他目标模块内都没有定义,那么链接器会报错。”

    # 一个全局符号引用在其他目标模块内都没有定义
    $ cat compile_correct1.cpp 
    int func2(int);

    int func(int val)
    {
      return val + func2(val);
    }
    $ cat main.cpp
    int main()
    {
        return 0;
    }
    # 编译器和汇编器不会报错
    $ g++ -o main main.o compile_correct
    $ g++ -c main.cpp -o main.o
    # 链接器会报错
    $ g++ -o main main.o compile_correct.o
    compile_correct.o: In function `func(int)':
    compile_correct1.cpp:(.text+0x11)undefined reference to `func2(int)'
    collect2errorld returned 1 exit status

    6)验证“如果同一个局部符号在同一个目标模块内有多个定义,那么编译器会报错,不会进行后面的汇编和链接过程。”

    # 局部变量 value(值为1的)与局部变量 value(值为2的)对应的局部符号的名称相同,属于“同一个局部符号在同一个目标模块内有多个定义”
    # 局部函数 int func(int val) 与 void func(int val) 对应的局部符号的名称相同,属于“同一个局部符号在同一个目标模块内有多个定义”
    # 局部变量 value(值为3的)与局部变量 value(值为4的)对应的局部符号的名称不相同
    $ cat compile_error3.cpp 
    static int value = 1;
    static double value = 2;

    static int func(int val)
    {
      static int value = 3;
      return val + value;
    }

    static void func(int val)
    {
      static int value = 4;
    }
    # 编译器会报错
    $ g++ -S compile_error3.cpp -o compile_error3.s
    compile_error3.cpp:2:15: error: conflicting declaration ‘double value
     static double value = 2;
                   ^~~~~
    compile_error3.cpp:1:12: note: previous declaration as ‘int value
     static int value = 1;
                ^~~~~
    compile_error3.cpp: In function ‘void func(int)’:
    compile_error3.cpp:10:13: error: ambiguating new declaration of ‘void func(int)’
     static void func(int val)
                 ^~~~
    compile_error3.cpp:4:12: note: old declaration ‘int func(int)’
     static int func(int val)
                ^~~~

    7)验证“如果一个局部符号引用在同一个目标模块内既没有声明也没有定义,那么编译器会报错,不会进行后面的汇编和链接过程。”

    # 一个局部符号引用在同一个目标模块内既没有声明也没有定义
    # 局部函数 func 在引用变量 value 的前面没有 value 变量的声明或定义
    $ cat compile_error4.cpp 
    static int func(int val)
    {
      return val + value;
    }

    static int value = 1;
    # 编译器会报错
    $ g++ -S compile_error4.cpp -o compile_error4.s
    compile_error4.cpp: In function ‘int func(int)’:
    compile_error4.cpp:3:16: error: ‘value’ was not declared in this scope
       return val + value
    ;
                    ^~~~~

    8)验证“如果一个局部符号引用在同一个目标模块内只有声明但没有定义,那么编译器会报错,不会进行后面的汇编和链接过程。”

    # 一个局部符号引用在同一个目标模块内只有声明但没有定义
    $ cat compile_error5.cpp 
    static int func2(int)
    ;

    static int func(int val)
    {
      return val + func2(val);
    }
    # 编译器会报错
    $ g++ -S compile_error5.cpp -o compile_error5.s
    compile_error5.cpp:1:12: warning: ‘int func2(int)’ used but never defined
     static int func2(int)
    ;
                ^~~~~

    链接器如何解析重复的全局符号名称

    编译器cc1将每个全局符号标记为强符号(Strong Symbol)或弱符号(Weak Symbol),汇编器将该标记信息隐式地编码在可重定位目标文件的符号表中(即用readelf查看符号表,其中Bind列值为WEAK的符号为弱符号,Ndx列值为COM的符号也被视为弱符号)。而编译器cc1plus将每个全局符号标记为强符号。

    注意: 强符号和弱符号都是针对符号定义来说的,不是针对符号引用。

    编译器cc1的默认行为:全局函数定义和已初始化的全局变量(包括初始值为 0 的全局变量)属于强符号,未初始化的全局变量属于弱符号。另外,默认行为可以通过-fno-common选项改变,更改后的行为:全局函数定义和全局变量(无论是否已初始化)都属于强符号。

    编译器cc1plus的默认行为:全局函数定义和全局变量(无论是否已初始化)都属于强符号。另外,默认行为不能通过-fcommon选项改变。

    注意: CS:APP 7.6.1 节中所描述的是,函数和已初始化的全局变量属于强符号,未初始化的全局变量属于弱符号。其原文为:

    Functions and initialized global variables get strong symbols. Uninitialized global variables get weak symbols.

    由于 CS:APP 中使用的编译驱动器是 gcc,并且源文件的后缀为.c。所以,在这种隐含的前提下,CS:APP 7.6.1 节中的上述描述是正确的。但在其他情况下(见下文),上述描述可能是错误的,这一点需要注意。

    Linux 链接器处理重复的全局符号名称的规则为:

    情形 编译驱动器 源文件后缀 实际调用的编译器 外部添加的编译选项 行为
    情形1 gcc .c cc1 无(即编译器 cc1 的默认行为)
  • 全局函数定义和已初始化的全局变量(包括初始值为0的全局变量)属于强符号
  • 未初始化的全局变量属于弱符号。
  • 情形2 gcc .c cc1 -fno-common 全局函数定义和全局变量(无论是否已初始化)都属于强符号。
    情形3 gcc .cpp cc1plus 无(即编译器 cc1plus 的默认行为) 全局函数定义和全局变量(无论是否已初始化)都属于强符号。
    情形4 g++ .c cc1plus 无(即编译器 cc1plus 的默认行为) 全局函数定义和全局变量(无论是否已初始化)都属于强符号。
    情形5 g++ .cpp cc1plus 无(即编译器 cc1plus 的默认行为) 全局函数定义和全局变量(无论是否已初始化)都属于强符号。

    1) 验证“编译器 cc1 的默认行为:全局函数定义和已初始化的全局变量(包括初始值为0的全局变量)属于强符号,未初始化的全局变量属于弱符号。”

    $ cat test1.c
    int g_val_1 = 0;
    int g_val_2;

    void func()
    {
      g_val_1 = 1;
      g_val_2 = 2;
    }


    $ cat main.c 
    #include <stdio.h>

    int g_val_1;
    int g_val_2 = 3;

    void func();

    int main()
    {
      func();
      printf("g_val_1=%d, g_val_2=%d\n", g_val_1, g_val_2);
      return 0;
    }


    $ gcc -c test1.c -o test1.o
    $ gcc -c main.c -o main.o
    $ readelf -s test1.o

    Symbol table '.symtab' contains 11 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         00000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         10000000000000000     0 FILE    LOCAL  DEFAULT  ABS test1.c
         20000000000000000     0 SECTION LOCAL  DEFAULT    1 
         30000000000000000     0 SECTION LOCAL  DEFAULT    3 
         40000000000000000     0 SECTION LOCAL  DEFAULT    4 
         50000000000000000     0 SECTION LOCAL  DEFAULT    6 
         60000000000000000     0 SECTION LOCAL  DEFAULT    7 
         70000000000000000     0 SECTION LOCAL  DEFAULT    5 
         80000000000000000     4 OBJECT  GLOBAL DEFAULT    4 g_val_1
         90000000000000004     4 OBJECT  GLOBAL DEFAULT  COM g_val_2
        100000000000000000    27 FUNC    GLOBAL DEFAULT    1 func

    $ readelf -s main.o

    Symbol table '.symtab' contains 14 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         00000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         10000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
         20000000000000000     0 SECTION LOCAL  DEFAULT    1 
         30000000000000000     0 SECTION LOCAL  DEFAULT    3 
         40000000000000000     0 SECTION LOCAL  DEFAULT    4 
         50000000000000000     0 SECTION LOCAL  DEFAULT    5 
         60000000000000000     0 SECTION LOCAL  DEFAULT    7 
         70000000000000000     0 SECTION LOCAL  DEFAULT    8 
         80000000000000000     0 SECTION LOCAL  DEFAULT    6 
         90000000000000004     4 OBJECT  GLOBAL DEFAULT  COM g_val_1
        100000000000000000     4 OBJECT  GLOBAL DEFAULT    3 g_val_2
        110000000000000000    50 FUNC    GLOBAL DEFAULT    1 main
        120000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND func
        130000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

    $ gcc -o main main.c test1.c -v
    #省略
    COLLECT_GCC_OPTIONS='-o' 'main' '-v' '-mtune=generic' '-march=x86-64'
     /usr/lib/gcc/x86_64-linux-gnu/6/cc1
    #省略

    $ ./main
    g_val_1=1, g_val_2=2

    2) 验证“编译器 cc1 添加 -fno-common 编译选项后的行为:全局函数定义和全局变量(无论是否已初始化)都属于强符号。”

    $ cat test1.c
    int g_val_1 = 0;
    int g_val_2;

    void func()
    {
      g_val_1 = 1;
      g_val_2 = 2;
    }


    $ cat main.c 
    #include <stdio.h>

    int g_val_1;
    int g_val_2 = 3;

    void func();

    int main()
    {
      func();
      printf("g_val_1=%d, g_val_2=%d\n", g_val_1, g_val_2);
      return 0;
    }


    $ gcc -c test1.c -o test1.o -fno-common
    $ gcc -c main.c -o main.o -fno-common
    $ readelf -s test1.o

    Symbol table '.symtab' contains 11 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         00000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         10000000000000000     0 FILE    LOCAL  DEFAULT  ABS test1.c
         20000000000000000     0 SECTION LOCAL  DEFAULT    1 
         30000000000000000     0 SECTION LOCAL  DEFAULT    3 
         40000000000000000     0 SECTION LOCAL  DEFAULT    4 
         50000000000000000     0 SECTION LOCAL  DEFAULT    6 
         60000000000000000     0 SECTION LOCAL  DEFAULT    7 
         70000000000000000     0 SECTION LOCAL  DEFAULT    5 
         80000000000000000     4 OBJECT  GLOBAL DEFAULT    4 g_val_1
         90000000000000004     4 OBJECT  GLOBAL DEFAULT    4 g_val_2
        100000000000000000    27 FUNC    GLOBAL DEFAULT    1 func

    $ readelf -s main.o

    Symbol table '.symtab' contains 14 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         00000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         10000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
         20000000000000000     0 SECTION LOCAL  DEFAULT    1 
         30000000000000000     0 SECTION LOCAL  DEFAULT    3 
         40000000000000000     0 SECTION LOCAL  DEFAULT    4 
         50000000000000000     0 SECTION LOCAL  DEFAULT    5 
         60000000000000000     0 SECTION LOCAL  DEFAULT    7 
         70000000000000000     0 SECTION LOCAL  DEFAULT    8 
         80000000000000000     0 SECTION LOCAL  DEFAULT    6 
         90000000000000000     4 OBJECT  GLOBAL DEFAULT    4 g_val_1
        100000000000000000     4 OBJECT  GLOBAL DEFAULT    3 g_val_2
        110000000000000000    50 FUNC    GLOBAL DEFAULT    1 main
        120000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND func
        130000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

    $ gcc -o main main.o test1.o
    test1.o:(.bss+0x0): multiple definition of `g_val_1'
    main.o:(.bss+0x0): first defined here
    test1.o:(.bss+0x4): multiple definition of `g_val_2'

    main.o:(.data+0x0): first defined here
    collect2: error: ld returned 1 exit status

    3) 验证“编译器 cc1plus 的默认行为:全局函数定义和全局变量(无论是否已初始化)都属于强符号。”

    $ cat test1.cpp 
    int g_val_1 = 0;
    int g_val_2;

    void func()
    {
      g_val_1 = 1;
      g_val_2 = 2;
    }


    $ cat main.cpp
    #include <stdio.h>

    int g_val_1;
    int g_val_2 = 3;

    void func();

    int main()
    {
      func();
      printf("g_val_1=%d, g_val_2=%d\n", g_val_1, g_val_2);
      return 0;
    }


    $ gcc -c test1.cpp -o test_cpp.o
    $ gcc -c main.cpp -o main_cpp.o
    $ readelf -s test_cpp.o

    Symbol table '.symtab' contains 11 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         00000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         10000000000000000     0 FILE    LOCAL  DEFAULT  ABS test1.cpp
         20000000000000000     0 SECTION LOCAL  DEFAULT    1 
         30000000000000000     0 SECTION LOCAL  DEFAULT    3 
         40000000000000000     0 SECTION LOCAL  DEFAULT    4 
         50000000000000000     0 SECTION LOCAL  DEFAULT    6 
         60000000000000000     0 SECTION LOCAL  DEFAULT    7 
         70000000000000000     0 SECTION LOCAL  DEFAULT    5 
         80000000000000000     4 OBJECT  GLOBAL DEFAULT    4 g_val_1
         90000000000000004     4 OBJECT  GLOBAL DEFAULT    4 g_val_2
        100000000000000000    27 FUNC    GLOBAL DEFAULT    1 _Z4funcv

    $ readelf -s main_cpp.o

    Symbol table '.symtab' contains 14 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         00000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         10000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.cpp
         20000000000000000     0 SECTION LOCAL  DEFAULT    1 
         30000000000000000     0 SECTION LOCAL  DEFAULT    3 
         40000000000000000     0 SECTION LOCAL  DEFAULT    4 
         50000000000000000     0 SECTION LOCAL  DEFAULT    5 
         60000000000000000     0 SECTION LOCAL  DEFAULT    7 
         70000000000000000     0 SECTION LOCAL  DEFAULT    8 
         80000000000000000     0 SECTION LOCAL  DEFAULT    6 
         90000000000000000     4 OBJECT  GLOBAL DEFAULT    4 g_val_1
        100000000000000000     4 OBJECT  GLOBAL DEFAULT    3 g_val_2
        110000000000000000    45 FUNC    GLOBAL DEFAULT    1 main
        120000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z4funcv
        130000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

    $ gcc -o main main_cpp.o test_cpp.o
    test_cpp.o:(.bss+0x0): multiple definition of `g_val_1'
    main_cpp.o:(.bss+0x0): first defined here
    test_cpp.o:(.bss+0x4): multiple definition of `g_val_2'

    main_cpp.o:(.data+0x0): first defined here
    collect2: error: ld returned 1 exit status

    $ gcc -o main main.cpp test1.cpp -v
    #省略
    COLLECT_GCC_OPTIONS='-o' 'main' '-v' '-mtune=generic' '-march=x86-64'
     /usr/lib/gcc/x86_64-linux-gnu/6/cc1plus
    #省略
    /tmp/ccIVPplI.o:(.bss+0x0): multiple definition of `g_val_1'
    /tmp/cc715sBV.o:(.bss+0x0): first defined here
    /tmp/ccIVPplI.o:(.bss+0x4): multiple definition of `g_val_2'

    /tmp/cc715sBV.o:(.data+0x0): first defined here
    collect2: error: ld returned 1 exit status

    链接器如何使用静态库来解析外部符号引用

    Linux 链接器ld使用静态库来解析可重定位目标文件中外部符号引用的规则为:链接器从左到右按照可重定位目标文件和静态库在编译驱动器的命令行上出现的顺序依次扫描,在扫描的过程中维护三个集合:a)一个可重定位目标文件的集合E(这个集合中的文件会被合并起来生成可执行目标文件);b)一个未解析的符号(即引用了但是尚未定义的符号)集合U;c)一个在前面输入文件中已定义的符号集合D。初始时,集合EUD均为空。

    链接器对每个输入文件的处理规则为:

    上述 Linux 链接器ld所使用的规则会带来这样一个问题:如果定义一个符号的库出现在引用这个符号的目标文件之前,那么引用就不能被解析,链接会失败。因此,可重定位目标文件和静态库在编译驱动器命令行中出现的先后顺序就非常重要了。

    关于库的一般准则是将它们放在编译驱动器命令行的末尾。对于有循环依赖的两个库而言,比如:liba.a 依赖 libb.a,libb.a 也依赖 liba.a,那么这两个库的在编译驱动器命令行中的先后顺序必须为:liba.a libb.a liba.alibb.a liba.a libb.a

    符号解析引发的问题

    编译器 cc1 的默认行为容易导致一些诡异的错误。比如:全局变量的值被意外地修改,可以分为两种情形:全局变量的值被同名的全局变量修改全局变量的值被不同名的全局变量修改

    1)全局变量的值被同名的全局变量修改

    $ cat test1.c
    int g_val_1 = 0;
    int g_val_2;

    void func()
    {
      g_val_1 = 1;
      g_val_2 = 2;
    }

    $ cat main.c 
    #include <stdio.h>

    int g_val_1;
    int g_val_2 = 3;

    void func();

    int main()
    {
      func();
      printf("g_val_1=%d, g_val_2=%d\n", g_val_1, g_val_2);
      return 0;
    }

    $ gcc -c test1.c -o test1.o
    $ gcc -c main.c -o main.o
    $ gcc -o main test1.o main.o
    $ ./main
    g_val_1=1, g_val_2=2

    main.c 中定义的全局变量 g_val_2 的值被 test1.c 中定义的 func() 函数由 3 改为 2 了。

    2)全局变量的值被不同名的全局变量修改

    $ cat test2.c 
    double g_val_1;

    void func()
    {
      g_val_1 = 1.1;
    }

    $ cat main2.c 
    #include <stdio.h>

    int g_val_0 = 1;
    int g_val_1 = 2;
    int g_val_2 = 3;

    void func();

    int main()
    {
      func();
      printf("g_val_0=%d, g_val_1=%d, g_val_2=%d\n", g_val_0, g_val_1, g_val_2);
      return 0;
    }
    $ gcc -o main2 test2.c main2.c
    /usr/bin/x86_64-linux-gnu-ld: Warning: alignment 4 of symbol `g_val_1' in /tmp/ccIc3noO.o is smaller than 8 in /tmp/ccXjyx8c.o
    /usr/bin/x86_64-linux-gnu-ld: Warning: size of symbol `g_val_1' changed from 8 in /tmp/ccXjyx8c.o to 4 in /tmp/ccIc3noO.o

    $ ./main2
    g_val_0=1, g_val_1=-1717986918, g_val_2=1072798105

    main2.c 中定义的全局变量 g_val_2 的值被意外地修改了。


    显式地指定函数和全局变量为弱符号

    gcc / g++ 支持显式地将一个函数和(无论是否已初始化的)全局变量指定为弱符号,可以理解为一个钩子(hook),从而支持在链接期覆盖这个符号的实现。

    示例 1,将一个函数显式地设置为弱符号:

    int __attribute ((weak)sum(intint)
    {
      return a + b;
    }

    示例 2,将一个(已初始化的)全局变量显式地设置为弱符号:

    int __attribute ((weak)) g_val 1;

    下一篇:计算机系统篇之链接(5):静态链接(下)——重定位

    上一篇:计算机系统篇之链接(3):静态链接(上)

    首页