有三个文件:
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: 在函数 'bar' 中:
funcB.c:(.text+0x0):“bar”的多重定义;/tmp/ccoiI4Pf.o:funcA.c:(.text+0x0):首先在这里定义
collect2:错误:ld 返回 1 个退出状态
使用 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: 在函数 'bar' 中:
funcB.c:(.text+0x0):“bar”的多重定义;/tmp/ccoiI4Pf.o:funcA.c:(.text+0x0):首先在这里定义
collect2:错误:ld 返回 1 个退出状态
如果删除 funcA.c 和 funcB.c 中重复定义的柱函数,则可以正常编译,而不会显示错误消息。
funcA 和 funcB 函数都链接到可执行文件中,尽管它们没有被调用。
$ gcc -o test main.c funcA.c funcB.c
$ 纳米 -g 测试
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 函数
0000000000001143 T 函数
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型主
0000000000001040 T _start
0000000000004010 D __TMC_END__
然后,我们恢复到原始的柱函数定义函数符号的故事,它在 funcA.c 和 funcB.c 中有柱函数定义。
然后编译成一个对象
文件函数符号的故事,然后将 obj 文件的两个文件打包到一个静态库中,并使用静态库生成可执行文件。
由于 main 函数不调用两个文件中的函数,因此生成的可执行文件不包含两个 obj 文件的符号,这与上述使用源文件或 obj 文件的结果不同。
此外,funcA.o 和 funcB.o 中的重复柱函数定义不会报告错误,因为两个 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 函数
U _GLOBAL_OFFSET_TABLE_
U 把
funcB.o:0000000000000000
T bar
0000000000000017 T 函数
U _GLOBAL_OFFSET_TABLE_
U 把
$ gcc -o test main.c func.a
$ 纳米 -g 测试
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型主
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
那么如果我们在主函数中调用库函数呢?
主.c
int main()
{
funcA();
返回 0;
}
构建可执行文件:
$ gcc -o test main.c func.a
$ 纳米 -g 测试
0000000000001162 T 棒
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 函数
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型主
U puts@@GLIBC_2.2.5
0000000000001060 T _start
0000000000004010 D __TMC_END__
它可以正确生成,并在调用的 funcA.o 中包含 funcA 函数,以及未使用的柱函数,即包含 funcA.o 的内容。
如果在 funcB.o 中未调用任何内容,则不包括该函数,因此不会重复柱函数。
再次修改主函数并调用柱函数。
主.c
int main()
{
funcA();
条();
返回 0;
}
构建可执行文件:
$ gcc -o test main.c func.a
$ 纳米 -g 测试
000000000000116c T 棒
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 函数
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型主
U puts@@GLIBC_2.2.5
0000000000001060 T _start
0000000000004010 D __TMC_END__
结果与前面的示例类似,funcA.o 包含所有内容,包括调用的 funcA 和 bar 函数。
在 funcB.o 中,柱线的功能重要吗?
这与我们打包的顺序有关,如果我们先打包 funcB.o 文件,那么链接器在寻找 main 函数要调用的柱函数时会首先找到 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 函数
U _GLOBAL_OFFSET_TABLE_
U 把
funcA.o:0000000000000000
T bar
0000000000000017 T 函数
U _GLOBAL_OFFSET_TABLE_
U 把
$ gcc -o test main.c func.a/
usr/bin/ld: func.a(funcA.o): 在函数 'bar' 中:
funcA.c:(.text+0x0):“bar”的多重定义;func.a(funcB.o):funcB.c:(.text+0x0):首先在这里定义
collect2:错误:ld 返回 1 个退出状态
总结:
1. 生成可执行文件时,
如果源文件或 OBJ 文件参与,则在连接连接过程时,其内容将包含在可执行文件中。
2、如果是
一个静态库,它基于里面的OBJ文件,如果使用就包含在内,如果不使用则不包括在内。
3. 链接器将在静态库中查找引用的函数或变量的定义,以先找到者为准,找到后不会继续搜索。
查找的顺序(在案例中)
多个静态库,按一阶顺序排列。如果它位于静态库内,则它是 obj 文件的打包顺序。
4. 静态库中可以有重复的定义,只要它所在的 obj 文件没有同时使用。
关于使用静态库参数编译程序
也可以使用以下命令生成上述可执行程序:
$ ar rcs libfunc.a *.o
$ gcc -o test main.c -lfunc -L./