Подтвердить что ты не робот

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

Когда я вижу код сборки приложения 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:
4b9b3361

Ответ 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