По какой причине имена функций имеют префикс с подчеркиванием компилятором?
Когда я вижу код сборки приложения на C, вот так:
emacs hello.c
clang -S -O hello.c -o hello.s
cat hello.s
Имена функций имеют префикс с подчеркиванием (например, callq _printf
). Почему это сделано и какие у него есть преимущества?
Пример:
Привет
#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:
3 ответа
От компоновщиков и загрузчиков:
В то время, когда UNIX был переписан на C примерно в 1974 году, его авторы уже имели обширные библиотеки на языке ассемблера, и было проще манипулировать именами нового C и C-совместимого кода, чем вернуться назад и исправить весь существующий код. Теперь, 20 лет спустя, код ассемблера был переписан пять раз, и компиляторы UNIX C, особенно те, которые создают объектные файлы COFF и ELF, больше не ставят подчеркивание.
Подчеркивание подчеркивания в результатах сборки C-компиляции - это всего лишь соглашение об именовании, которое возникло как обходной путь. Он застрял (насколько мне известно) без особой причины, и теперь пробился в Кланг.
Вне сборки стандартная библиотека C часто имеет определяемые реализацией функции с префиксом подчеркивания, чтобы передать понятия волшебства и не касаться этого обычным программистам, которые сталкиваются с ними.
Многие компиляторы использовали для перевода C на язык ассемблера, а затем запускали ассемблер для создания объектного файла. Это намного проще, чем генерировать двоичный код напрямую. (AFAIK GCC все еще делает это. Но у него также есть свой собственный ассемблер.) Во время этого перевода имена функций становятся метками в источнике сборки. Если у вас есть функция называется (например) ret
однако некоторые ассемблеры могут запутаться и подумать, что это инструкция, а не метка. (Например, YASM, в основном потому, что ярлыки могут появляться где угодно и не требуют двоеточий. $
если вы хотите ярлык под названием ret
.)
Предварительно добавить символ (например, подчеркивание) к сгенерированным C-меткам было намного проще, чем написать свой собственный C-friendly ассемблер или беспокоиться о конфликте меток с инструкциями / директивами по сборке.
В наши дни ассемблеры и компиляторы немного эволюционировали, и большинство людей все равно работают на уровне C или выше. Таким образом, первоначальная потребность искажать имена в Си в значительной степени исчезла.
На первый взгляд, операционная система Unix/Unix-подобная, работающая на ПК. По моему мнению, нет ничего удивительного в том, чтобы найти _printf в сгенерированном ассемблере. C printf - это функция, которая выполняет ввод / вывод. Так что ядро + драйвер отвечает за выполнение запрошенного ввода-вывода.
Путь машинных инструкций, взятый на любой Unix/Unix-подобной ОС, следующий:
printf (код C)-> _printf (libc) -> trap -> ядро + работа драйвера -> возврат из trap -> возврат из _printf (libc) -> завершение printf и возврат -> следующая машинная инструкция в коде C
В случае этого извлечения кода сборки, похоже, что C printf встроен компилятором, из-за которого точка входа _printf была видна в коде сборки.
Чтобы убедиться, что printf C не украшен префиксом (в данном случае подчеркиванием), лучше всего, если во всех заголовках C выполняется поиск _printf с помощью такой команды:
найти /usr/include -name *.h -exec grep _printf {} \; -Распечатать