有如下三个文件:
- funcA.c:
-
-
- void bar( ) {
-
- }
-
-
- void funcA () {
-
- }
-
-
-
- funcB.c:
-
-
- void bar( ) {
-
- }
-
-
- void funcB () {
-
- }
-
-
- main.c
-
- int main() {
-
- return 0;
-
- }
编译:
$ 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./