В чем причина, по которой имена функций имеют префикс подчёркивания компилятором?
Когда я вижу код сборки приложения 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:
Ответы
Ответ 1
От Коннекторы и загрузчики:
В то время, когда UNIX была переписана на C примерно в 1974 году, у ее авторов уже были обширные библиотеки языков участников, и было легче манипулировать именами новых C и C-совместимых кодов, чем возвращаться и исправлять все существующие код. Теперь, через 20 лет, код ассемблера был переписан пять раз, а компиляторы UNIX C, особенно те, которые создают объектные файлы COFF и ELF, больше не добавляют символ подчеркивания.
Приостановка подчеркивания в результатах сборки компиляции C - это просто соглашение об именах, которое возникло в качестве обходного пути. Он застрял вокруг (насколько мне известно) никакой особой причины и теперь пробился в Кланг.
Вне сборки в стандартной библиотеке C часто есть определенные с реализацией функции с префиксом подчеркивания, чтобы передать понятия магичности и не трогать это обычным программистам, которые наткнулись на них.
Ответ 2
Множество компиляторов, используемых для перевода языка C на ассемблер, а затем запустить ассемблер для создания объектного файла. Это намного проще, чем генерировать двоичный код напрямую. (AFAIK GCC все еще это делает, но у него также есть собственный ассемблер.) Во время этого перевода имена функций становятся метками в источнике сборки. Если у вас есть функция, называемая (например) ret
, некоторые сборщики могут запутаться и считать ее инструкцией, а не меткой. (Например, YASM, в основном потому, что ярлыки могут появляться практически везде и не требуют двоеточий. Вам нужно добавить $
, если вы хотите, чтобы метка называлась ret
.)
Превращение символа (например, подчеркивание) на сгенерированные С-метки было намного проще, чем писать собственный C-удобный ассемблер или беспокоиться о том, что метки сталкиваются с инструкциями/директивами сборки.
В наши дни сборщики и компиляторы немного изменились, и большинство людей работают на уровне C или выше. Таким образом, первоначальная необходимость манипулировать именами в C в значительной степени ушла.
Ответ 3
На первый взгляд операционная система работает как Unix/Unix на ПК. По мне, нет ничего удивительного в том, чтобы найти _printf в сгенерированном языке ассемблера. C printf - это функция, которая выполняет ввод-вывод. Поэтому драйвер kernel + отвечает за выполнение запрошенного ввода-вывода.
Путь команд машины, принятый на любой Unix/Unix-подобной ОС, следующий:
printf (C-код) → _printf (libc) → trap → kernel + driver work → return from trap → return from _printf (libc) → printf завершение и возврат → следующая машинная инструкция в коде C
В случае с этим извлечением кода сборки, похоже, что C printf встроен компилятором, который запустил точку входа _printf в код сборки.
Чтобы убедиться, что C printf не украшен префиксом (в этом случае подчеркивание), лучше всего искать во всех заголовках C для _printf с помощью следующей команды:
find/usr/include -name *.h -exec grep _printf {} \; -print