编译器如何选择 link 的函数?
How does a compiler choose which function to link?
我有一个程序(main.c
):
#include <stdio.h>
#include <math.h>
int main() {
int result = sqrt(9);
printf("result: %d\n" ,result);
return 0;
}
double sqrt(double blah) {
return 0;
}
当我运行这个的时候,我的结果是
result: 3
这会告诉我链接器正在选择 libm
库的 sqrt
函数而不是我的函数来调用我的 main
函数。
在启用所有警告的情况下编译此程序时,我没有收到任何错误或警告:
gcc main.c -Wall
我的问题:
- 为什么链接器不选择我对
sqrt
的定义来调用?
- 这是确定性的吗?
- 为什么我没有收到任何错误或警告?似乎具有相同签名的函数的多个定义是一个陷阱,应该以某种方式指出。
- 有没有办法输出哪些函数链接到了哪里?所以如果我遇到引用了非预期定义的情况,我可以调试吗?
我唯一能想到的是当我运行gcc --precompile
,我看到这个函数声明:
extern double sqrt(double);
这是否告诉链接器 sqrt 是在该文件之外定义的?并且由于这已经满足sqrt
的定义,所以在链接时忽略了我自己的定义?
gcc 信息(我知道它真的很响,因为我在 mac,不确定这是否对这个问题有影响)
gcc --version
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/4.2.1
Apple clang version 11.0.0 (clang-1100.0.33.17)
Target: x86_64-apple-darwin19.6.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
编辑:
汇编输出:
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 15 sdk_version 10, 15
.section __TEXT,__literal8,8byte_literals
.p2align 3 ## -- Begin function main
LCPI0_0:
.quad 4621256167635550208 ## double 9
.section __TEXT,__text,regular,pure_instructions
.globl _main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq , %rsp
movsd LCPI0_0(%rip), %xmm0 ## xmm0 = mem[0],zero
movl [=16=], -4(%rbp)
sqrtsd %xmm0, %xmm0
cvttsd2si %xmm0, %eax
movl %eax, -8(%rbp)
movl -8(%rbp), %esi
leaq L_.str(%rip), %rdi
movb [=16=], %al
callq _printf
xorl %esi, %esi
movl %eax, -12(%rbp) ## 4-byte Spill
movl %esi, %eax
addq , %rsp
popq %rbp
retq
.cfi_endproc
## -- End function
.globl _sqrt ## -- Begin function sqrt
.p2align 4, 0x90
_sqrt: ## @sqrt
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
movsd %xmm0, -8(%rbp)
xorps %xmm0, %xmm0
popq %rbp
retq
.cfi_endproc
## -- End function
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "result: %d\n"
.subsections_via_symbols
标准C库函数的名称保留用作外部链接的标识符。您不应该将它们用于您自己的功能。这意味着当您使用保留名称时,编译器可能会假定它是标准函数,而不是您自己的实现。然后,为了优化您的程序(即使没有打开完全优化),编译器可能会用处理器的平方根指令替换对 sqrt
的调用。或者编译器甚至可以自己计算结果并将其构建到汇编代码中。
有个问题你还不知道:
链接可执行文件时,您指定了 -lm
以提供 sqrt()
函数的两个版本,即您提供的版本和 libm.so
共享可执行文件。
问题是共享可执行文件可能会优先,因为链接是在加载时完成的,您的函数是一个内部函数,并且编译器在关闭已编译的 .o
之前没有解析引用。这使得 select 的动态链接器成为共享库加载时的函数之一。
当没有共享二进制文件时,sqrt()
函数确实在存储在 libm.a
中的 sqrt.o
文件中,并且链接器 select 只编辑了来自档案的二进制文件正在解决一些未解决的引用,在这种情况下,libm.a
中的 sqrt.o
应该没有被包含,而你的 sqrt()
应该被引用。
顺便说一下,编译器如何处理 sqrt()
函数也存在一个问题,因为编译器接受支持所谓的内部函数(因为 sqrt()
是)他们通常优先考虑因此,以某种方式建议您不要像内部函数那样命名您的函数。这方面在 FORTRAN 中得到了很好的扩展,直到现在才成为问题。编译器对待内在函数的方式与其他人完全不同......你会发现其他库函数不会发生这种情况。
如果您尝试将 sqrt()
定义为不同的函数(例如,平方根的 unsigned sqrt(unsigned n);
整数版本),您会看到编译器发出警告编译器会发出警告,建议您重命名函数,以免使用与内部函数相同的名称。我没有深入挖掘这一点,但不知何故,编译器在任何定义 #include
d.
之前就知道某些函数的存在
我有一个程序(main.c
):
#include <stdio.h>
#include <math.h>
int main() {
int result = sqrt(9);
printf("result: %d\n" ,result);
return 0;
}
double sqrt(double blah) {
return 0;
}
当我运行这个的时候,我的结果是
result: 3
这会告诉我链接器正在选择 libm
库的 sqrt
函数而不是我的函数来调用我的 main
函数。
在启用所有警告的情况下编译此程序时,我没有收到任何错误或警告:
gcc main.c -Wall
我的问题:
- 为什么链接器不选择我对
sqrt
的定义来调用?- 这是确定性的吗?
- 为什么我没有收到任何错误或警告?似乎具有相同签名的函数的多个定义是一个陷阱,应该以某种方式指出。
- 有没有办法输出哪些函数链接到了哪里?所以如果我遇到引用了非预期定义的情况,我可以调试吗?
我唯一能想到的是当我运行gcc --precompile
,我看到这个函数声明:
extern double sqrt(double);
这是否告诉链接器 sqrt 是在该文件之外定义的?并且由于这已经满足sqrt
的定义,所以在链接时忽略了我自己的定义?
gcc 信息(我知道它真的很响,因为我在 mac,不确定这是否对这个问题有影响)
gcc --version
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/4.2.1
Apple clang version 11.0.0 (clang-1100.0.33.17)
Target: x86_64-apple-darwin19.6.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
编辑:
汇编输出:
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 15 sdk_version 10, 15
.section __TEXT,__literal8,8byte_literals
.p2align 3 ## -- Begin function main
LCPI0_0:
.quad 4621256167635550208 ## double 9
.section __TEXT,__text,regular,pure_instructions
.globl _main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq , %rsp
movsd LCPI0_0(%rip), %xmm0 ## xmm0 = mem[0],zero
movl [=16=], -4(%rbp)
sqrtsd %xmm0, %xmm0
cvttsd2si %xmm0, %eax
movl %eax, -8(%rbp)
movl -8(%rbp), %esi
leaq L_.str(%rip), %rdi
movb [=16=], %al
callq _printf
xorl %esi, %esi
movl %eax, -12(%rbp) ## 4-byte Spill
movl %esi, %eax
addq , %rsp
popq %rbp
retq
.cfi_endproc
## -- End function
.globl _sqrt ## -- Begin function sqrt
.p2align 4, 0x90
_sqrt: ## @sqrt
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
movsd %xmm0, -8(%rbp)
xorps %xmm0, %xmm0
popq %rbp
retq
.cfi_endproc
## -- End function
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "result: %d\n"
.subsections_via_symbols
标准C库函数的名称保留用作外部链接的标识符。您不应该将它们用于您自己的功能。这意味着当您使用保留名称时,编译器可能会假定它是标准函数,而不是您自己的实现。然后,为了优化您的程序(即使没有打开完全优化),编译器可能会用处理器的平方根指令替换对 sqrt
的调用。或者编译器甚至可以自己计算结果并将其构建到汇编代码中。
有个问题你还不知道:
链接可执行文件时,您指定了 -lm
以提供 sqrt()
函数的两个版本,即您提供的版本和 libm.so
共享可执行文件。
问题是共享可执行文件可能会优先,因为链接是在加载时完成的,您的函数是一个内部函数,并且编译器在关闭已编译的 .o
之前没有解析引用。这使得 select 的动态链接器成为共享库加载时的函数之一。
当没有共享二进制文件时,sqrt()
函数确实在存储在 libm.a
中的 sqrt.o
文件中,并且链接器 select 只编辑了来自档案的二进制文件正在解决一些未解决的引用,在这种情况下,libm.a
中的 sqrt.o
应该没有被包含,而你的 sqrt()
应该被引用。
顺便说一下,编译器如何处理 sqrt()
函数也存在一个问题,因为编译器接受支持所谓的内部函数(因为 sqrt()
是)他们通常优先考虑因此,以某种方式建议您不要像内部函数那样命名您的函数。这方面在 FORTRAN 中得到了很好的扩展,直到现在才成为问题。编译器对待内在函数的方式与其他人完全不同......你会发现其他库函数不会发生这种情况。
如果您尝试将 sqrt()
定义为不同的函数(例如,平方根的 unsigned sqrt(unsigned n);
整数版本),您会看到编译器发出警告编译器会发出警告,建议您重命名函数,以免使用与内部函数相同的名称。我没有深入挖掘这一点,但不知何故,编译器在任何定义 #include
d.