编译器在函数名称前加上下划线的原因是啥?

Posted

技术标签:

【中文标题】编译器在函数名称前加上下划线的原因是啥?【英文标题】:What is the reason function names are prefixed with an underscore by the compiler?编译器在函数名称前加上下划线的原因是什么? 【发布时间】:2011-08-19 23:55:05 【问题描述】:

当我看到一个 C 应用程序的汇编代码时,像这样:

emacs hello.c
clang -S -O hello.c -o hello.s
cat hello.s

函数名称以下划线为前缀(例如callq _printf)。为什么要这样做,有什么好处?


例子:

hello.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int main() 
  char *myString = malloc(strlen("Hello, World!") + 1);
  memcpy(myString, "Hello, World!", strlen("Hello, World!") + 1);
  printf("%s", myString);
  return 0;

hello.s

_main:                       ; Here
Leh_func_begin0:
    pushq   %rbp
Ltmp0:
    movq    %rsp, %rbp
Ltmp1:
    movl    $14, %edi
    callq   _malloc          ; Here
    movabsq $6278066737626506568, %rcx
    movq    %rcx, (%rax)
    movw    $33, 12(%rax)
    movl    $1684828783, 8(%rax)
    leaq    L_.str1(%rip), %rdi
    movq    %rax, %rsi
    xorb    %al, %al
    callq   _printf          ; Here
    xorl    %eax, %eax
    popq    %rbp
    ret
Leh_func_end0:

【问题讨论】:

我很想知道这是哪个操作系统。我以为这种愚蠢的做法早就被抛弃了。也许是 Mac? OSX 和其他一些 BSD 衍生产品是我所知道的唯一可能仍然这样做的类 unix 操作系统。 Linux 在大约 12 到 15 年前肯定放弃了它。 @R..:你是说a.out做了那个 Why do C compilers prepend underscores to external names? 的可能重复项 【参考方案1】:

乍一看,操作系统是在 PC 上运行的类 Unix/Unix。据我说,在生成的汇编语言中找到 _printf 并没有什么令人惊讶的。 C printf 是一个执行 I/O 的函数。因此,执行请求的 I/O 是内核 + 驱动程序的责任。

在任何 Unix/类 Unix 操作系统上采用的机器指令路径如下:

printf (C 代码)-> _printf (libc) -> 陷阱 -> 内核 + 驱动工作 -> 从陷阱返回 -> 从 _printf (libc) 返回 -> printf 完成并返回 -> C 代码中的下一条机器指令

在此汇编代码提取的情况下,C printf 似乎被编译器内联,导致 _printf 入口点在汇编代码中可见。

为确保 C printf 不会被前缀(在本例中为下划线)修饰,最好使用如下命令在所有 C 标头中搜索 _printf:

找到 /usr/include -name *.h -exec grep _printf \; -打印

【讨论】:

【参考方案2】:

许多编译器用于将 C 转换为汇编语言,然后在其上运行汇编程序以生成目标文件。这比直接生成二进制代码要容易得多。 (AFAIK GCC 仍然这样做。但它也有自己的汇编程序。)在此翻译过程中,函数名称成为汇编源代码中的标签。但是,如果您有一个名为(例如)ret 的函数,一些汇编程序可能会感到困惑,并认为它是指令而不是标签。 (例如,YASM 确实如此,主要是因为标签几乎可以出现在任何地方并且不需要冒号。如果您想要一个名为 ret 的标签,则必须在前面加上 $。)

在 C 生成的标签前添加一个字符(比如下划线)比编写自己的 C 友好汇编程序或担心标签与汇编指令/指令冲突要容易得多。

如今,汇编器和编译器已经有了一些发展,而且大多数人都在 C 级别或更高级别工作。因此,最初在 C 中修改名称的需求已基本消失。

【讨论】:

【参考方案3】:

来自Linkers and Loaders:

在 1974 年左右用 C 重写 UNIX 时,它的作者已经拥有大量的汇编语言库,而且更容易破坏新的 C 和 C 兼容代码的名称,而不是返回并修复所有现有的代码。 20 年后的今天,汇编代码已经全部重写了五次,UNIX C 编译器,尤其是创建 COFF 和 ELF 目标文件的编译器,不再前置下划线。

在 C 编译的汇编结果中添加下划线只是作为一种解决方法出现的名称修改约定。它停留在(据我所知)没有特别的原因,现在已经进入 Clang。

在汇编之外,C 标准库通常具有以下划线为前缀的实现定义函数,以向偶然发现它们的普通程序员传达神奇的概念,不要碰这个。 p>

【讨论】:

至于 C 源代码中的前导下划线:这是一个名称间距问题,参见。 C 标准的第 7.1.3 节。说得更直白一点:如果你的 C 代码定义了一个以两个下划线开头或一个下划线后跟一个大写字母的标识符,那么它是损坏的。一个下划线,它是损坏的。那些是为编译器和标准库实现保留的。 不过,如果您正在为标准库实现编写 C 代码,则不会损坏。换句话说,你真的需要知道你在做什么,如果你不能解释为什么你在做什么是好的,那么你做错了。但是以防万一您正在编写库,编译器不会阻止您违反这些规则,以防万一有人想知道为什么。 @Lars:不幸的是,在半系统级但不是标准库代码中存在许多自大的问题,例如遗留 X 库、声音库、图形库等。他们有权使用下划线,就好像它们是标准库的一部分一样......然后有些人在不理解的情况下盲目地从标准库的各种实现中导入代码,并保留下划线......这些用法绝对是坏了。 我是个新手,对 C 不太了解。有人可以解释一下什么是损坏的标识符吗?我到处都看到这些下划线,不明白为什么要使用它们。 @ChristopherCreutzig 您指的是哪个版本的标准? ANSI C88 没有 7.1.3。 flash-gordon.me.uk/ansi.c.txt没关系。我找到了 C99 ISO 标准 9899

以上是关于编译器在函数名称前加上下划线的原因是啥?的主要内容,如果未能解决你的问题,请参考以下文章

js中“[]”和“”的区别是啥?

应用加载失败(loadlibrary失败 ,)应该怎么解决

请问vc中为啥有的函数前缀为下划线,有的函数无下划线?两者有啥区别?

C语言中在函数名或关键字钱加下划线是啥意思?

函数初识

rust替换函数名