当我看到C app的汇编代码时,如下所示:
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:
答案 0 :(得分:29)
在1974年左右用C语言重写UNIX时,它的作者已经有了广泛的声明语言库,并且更容易破坏新的C和C兼容代码的名称,而不是返回并修复所有现有的码。现在,20年后,汇编程序代码全部被重写了五次,而UNIX C编译器,特别是那些创建COFF和ELF目标文件的编译器,不再添加下划线。
在C编译的汇编结果中预先加下下划线只是一个名称错误的约定,它作为一种变通方法而出现。它(据我所知)没有特别的原因,现在已经进入了Clang。
在汇编之外,C标准库通常具有以下划线为前缀的实现定义函数,以传达神奇性的概念,并且不要将这个触及偶然发现它们的普通程序员。
答案 1 :(得分:5)
许多编译器用于将C转换为汇编语言,然后在其上运行汇编程序以生成目标文件。它比直接生成二进制代码容易得多。 (AFAIK GCC仍然这样做。但它也有自己的汇编程序。)在此转换过程中,函数名称成为汇编源代码中的标签。但是,如果你有一个名为(例如)ret
的函数,一些汇编程序可能会感到困惑并认为它是一个指令而不是一个标签。 (例如,YASM主要是因为标签几乎可以出现在任何地方而且不需要冒号。如果你想要一个名为$
的标签,你必须预先加ret
。)
将一个字符(比如说下划线)添加到C生成的标签上比编写一个自己的C友好的汇编器或担心标签与汇编指令/指令冲突要容易得多。
现在,汇编程序和编译器已经发展了一些,大多数人无论如何都在C级或更高级别工作。因此,在C中破坏名字的最初需要基本消失了。
答案 2 :(得分:-1)
乍一看,操作系统在PC上运行类Unix / Unix。据我所知,在生成的汇编语言中找到_printf并不奇怪。 C printf是执行I / O的功能。因此内核+驱动程序负责执行请求的I / O.
在任何Unix / Unix类操作系统上采用的机器指令路径如下:
printf(C代码) - &gt; _printf(libc) - &gt;陷阱 - &gt;内核+驱动程序工作 - &gt;从陷阱返回 - &gt;从_printf(libc)返回 - &gt; printf完成并返回 - &gt; C代码中的下一个机器指令
在这个汇编代码提取的情况下,看起来C printf由compilateur内联,这导致_printf入口点在汇编代码中可见。
为了确保C printf没有使用前缀(在本例中为下划线)进行修饰,最好使用以下命令在所有C头中搜索_printf:
find / usr / include -name * .h -exec grep _printf {} \; -print