• 编程参考- 重名符号的链接问题


    有如下三个文件:

    1. funcA.c:
    2. void bar( ) {
    3. }
    4. void funcA () {
    5. }
    6. funcB.c:
    7. void bar( ) {
    8. }
    9. void funcB () {
    10. }
    11. main.c
    12. int main() {
    13.   return 0;
    14. }

    编译:

    $ gcc -o test main.c funcA.c funcB.c

    /usr/bin/ld: /tmp/cczNqgbd.o: in function `bar':

    funcB.c:(.text+0x0): multiple definition of `bar'; /tmp/ccoiI4Pf.o:funcA.c:(.text+0x0): first defined here

    collect2: error: ld returned 1 exit status

    使用C文件进行编译时,因为bar函数有重复定义,所以链接出错。

    把C文件先编译成obj文件,也是一样错误。

    $ gcc -c funcA.c

    $ gcc -c funcB.c

    $ gcc -o test main.c funcA.o funcB.o

    /usr/bin/ld: /tmp/cczNqgbd.o: in function `bar':

    funcB.c:(.text+0x0): multiple definition of `bar'; /tmp/ccoiI4Pf.o:funcA.c:(.text+0x0): first defined here

    collect2: error: ld returned 1 exit status

    如果把funcA.c和funcB.c里面的重复定义的bar函数删掉,则能正常编译,没有错误提示。

    并且将funcA和funcB两个函数都链接进了可执行文件里,虽然并没有调用。

    $ gcc -o test main.c funcA.c funcB.c

    $ nm -g test

    0000000000004010 B __bss_start

                     w __cxa_finalize@@GLIBC_2.2.5

    0000000000004000 D __data_start

    0000000000004000 W data_start

    0000000000004008 D __dso_handle

    0000000000004010 D _edata

    0000000000004018 B _end

    00000000000011c8 T _fini

    0000000000001138 T funcA

    0000000000001143 T funcB

                     w __gmon_start__

    0000000000002000 R _IO_stdin_used

                     w _ITM_deregisterTMCloneTable

                     w _ITM_registerTMCloneTable

    00000000000011c0 T __libc_csu_fini

    0000000000001150 T __libc_csu_init

                     U __libc_start_main@@GLIBC_2.2.5

    0000000000001129 T main

    0000000000001040 T _start

    0000000000004010 D __TMC_END__

    然后,我们恢复最初的bar函数定义,funcA.c和funcB.c里都有bar函数定义。

    然后编译成obj文件,再把两个文件的obj文件打包成静态库,使用静态库来生成可执行文件。

    因为main函数里没有调用两文件中的函数,所以生成的可执行文件里就没包含两个obj文件的符号,这里就和上面的使用源文件或obj文件的结果不一样了。

    并且,funcA.o和funcB.o里面的重复的bar函数定义也不会报错,因为这两个obj文件都没有参与链接。

    $ gcc -c funcA.c

    $ gcc -c funcB.c

    $ ar rcs func.a func*.o

    $ nm func.a

    funcA.o:

    0000000000000000 T bar

    0000000000000017 T funcA

                     U _GLOBAL_OFFSET_TABLE_

                     U puts

    funcB.o:

    0000000000000000 T bar

    0000000000000017 T funcB

                     U _GLOBAL_OFFSET_TABLE_

                     U puts

    $ gcc -o test main.c func.a

    $ nm -g test

    0000000000004010 B __bss_start

                     w __cxa_finalize@@GLIBC_2.2.5

    0000000000004000 D __data_start

    0000000000004000 W data_start

    0000000000004008 D __dso_handle

    0000000000004010 D _edata

    0000000000004018 B _end

    00000000000011b8 T _fini

                     w __gmon_start__

    0000000000002000 R _IO_stdin_used

                     w _ITM_deregisterTMCloneTable

                     w _ITM_registerTMCloneTable

    00000000000011b0 T __libc_csu_fini

    0000000000001140 T __libc_csu_init

                     U __libc_start_main@@GLIBC_2.2.5

    0000000000001129 T main

    0000000000001040 T _start

    0000000000004010 D __TMC_END__

    注意,关于obj文件的打包:

    $ ar rcs func.a func*.o

    这个相当于

    $ ar rcs func.a funcA.o funcB.o

    所以,使用nm命令,显示的顺序就是打包的顺序,先是funcA.o,之后是funcB.o

    那如果我们main函数里调用了库里的函数呢?

    main.c

    int main()

    {

      funcA();

      return 0;

    }

    生成可执行文件:

    $ gcc -o test main.c func.a

    $ nm -g test

    0000000000001162 T bar

    0000000000004010 B __bss_start

                     w __cxa_finalize@@GLIBC_2.2.5

    0000000000004000 D __data_start

    0000000000004000 W data_start

    0000000000004008 D __dso_handle

    0000000000004010 D _edata

    0000000000004018 B _end

    0000000000001208 T _fini

    0000000000001179 T funcA

                     w __gmon_start__

    0000000000002000 R _IO_stdin_used

                     w _ITM_deregisterTMCloneTable

                     w _ITM_registerTMCloneTable

    0000000000001200 T __libc_csu_fini

    0000000000001190 T __libc_csu_init

                     U __libc_start_main@@GLIBC_2.2.5

    0000000000001149 T main

                     U puts@@GLIBC_2.2.5

    0000000000001060 T _start

    0000000000004010 D __TMC_END__

    可以正确生成,并且包含了被调用的funcA.o里面的funcA函数,另外没有使用的bar函数也包含了进来,也就是说将funcA.o的内容都包含进来了。

    funcB.o里面没有内容被调用,就都没有包含,所以bar函数重复也不用管。

    再修改一下main函数,调用bar函数。

    main.c

    int main()

    {

      funcA();

      bar();

      return 0;

    }

    生成可执行文件:

    $ gcc -o test main.c func.a

    $ nm -g test

    000000000000116c T bar

    0000000000004010 B __bss_start

                     w __cxa_finalize@@GLIBC_2.2.5

    0000000000004000 D __data_start

    0000000000004000 W data_start

    0000000000004008 D __dso_handle

    0000000000004010 D _edata

    0000000000004018 B _end

    0000000000001208 T _fini

    0000000000001183 T funcA

                     w __gmon_start__

    0000000000002000 R _IO_stdin_used

                     w _ITM_deregisterTMCloneTable

                     w _ITM_registerTMCloneTable

    0000000000001200 T __libc_csu_fini

    0000000000001190 T __libc_csu_init

                     U __libc_start_main@@GLIBC_2.2.5

    0000000000001149 T main

                     U puts@@GLIBC_2.2.5

    0000000000001060 T _start

    0000000000004010 D __TMC_END__

    结果和前一个例子类似,funcA.o的内容全都包含进来了,包括被调用的funcA和bar函数。

    那funcB.o里的bar函数有影响吗?

    这里关系到我们打包时的顺序,如果我们先打包funcB.o文件,那链接器在查找main函数要调用的bar函数时,先找到的就是funcB.o。

    而查找调用的funcA函数时,找到的就是funcA.o。

    那这两个文件就都要参与链接,这时因为bar函数重复定义,就会出错。

    $ gcc -c funcA.c

    $ gcc -c funcB.c

    $ ar rcs func.a funcB.o funcA.o

    $ nm func.a

    funcB.o:

    0000000000000000 T bar

    0000000000000017 T funcB

                     U _GLOBAL_OFFSET_TABLE_

                     U puts

    funcA.o:

    0000000000000000 T bar

    0000000000000017 T funcA

                     U _GLOBAL_OFFSET_TABLE_

                     U puts

    $ gcc -o test main.c func.a

    /usr/bin/ld: func.a(funcA.o): in function `bar':

    funcA.c:(.text+0x0): multiple definition of `bar'; func.a(funcB.o):funcB.c:(.text+0x0): first defined here

    collect2: error: ld returned 1 exit status

    总结:

    1,生成可执行文件时,如果是源文件或obj文件参与,则连接过程时,其内容都会被包含进可执行文件。

    2,如果是静态库的话,是以里面的obj文件为单位,如果用到了就包含,没用到就不包含。

    3,被引用的函数或变量,链接器会在静态库里查找定义,以最先找到的为准,找到后不会继续查找。

    查找的顺序,如果是多个静态库,就按照先后顺序。如果是静态库内部,就是obj文件打包的顺序。

    4,静态库里可以有重复的定义,只要其所在的obj文件不同时被使用即可。

    关于使用静态库参数程序编译

    上面的可执行程序的生成,也可以使用如下命令:

    $ ar rcs libfunc.a *.o

    $ gcc -o test main.c -lfunc -L./

  • 相关阅读:
    数论合集
    什么是会话劫持以及如何阻止它
    网络小说作家写手提问常用的ChatGPT通用提示词模板
    互联网大厂的测试员是怎么交付测试项目文档的
    展平数组(flatten、ravel)--numpy
    linux上redis升级
    计算机毕业设计SSMJava宠物之家【附源码数据库】
    转置卷积和膨胀卷积详细讲解
    学生管理系统之简化学生版(练习版)
    Java升级:JDK 9新特性全面解析“
  • 原文地址:https://blog.csdn.net/guoqx/article/details/127962766