По какой причине имена функций имеют префикс с подчеркиванием компилятором?

Когда я вижу код сборки приложения на 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 {} \; -Распечатать

Другие вопросы по тегам