编译器如何选择 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

我的问题:

我唯一能想到的是当我运行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); 整数版本),您会看到编译器发出警告编译器会发出警告,建议您重命名函数,以免使用与内部函数相同的名称。我没有深入挖掘这一点,但不知何故,编译器在任何定义 #included.

之前就知道某些函数的存在