Как GCC обрабатывает встроенную функцию

У меня есть проблемы с пониманием встроенных функций GCC и очень смущены.

  • В чем разница между библиотечной функцией и встроенной функцией?

  • Есть ли что-то, что может сделать встроенная функция, но функция библиотеки не может?

  • Можно ли написать библиотечную функцию, которая выполняет ту же задачу, что и встроенная функция printf? Как я могу указать тип входных параметров (% f, float или double)?

  • Машинные инструкции встроенных функций GCC не хранятся в библиотеке, правильно? Где они?

  • При компоновке, как вы можете контролировать, куда поместить эти встроенные функциональные коды?

  • Почему иногда я могу вызывать сообщения об ошибках типа "undefined ссылка на __builtin_stdarg_start" при компоновке

    // main.c
    #include <stdio.h>
    int main(void) {
      printf("hello world!\n");
      return 0;
    }
    

    gcc -c main.c, nm показывает, что в main.o нет символа printf. (только main (T) и puts (U)), почему?

Ответы

Ответ 1

В чем разница между библиотечной функцией и встроенной функцией?

Встроенная функция - это то, что компилятор имеет некоторые знания непосредственно внутри самого компилятора. Библиотечная функция просто определена в библиотеке. Встроенная функция и функция библиотеки с тем же именем могут существовать, поэтому для остальных ваших вопросов я буду рассматривать "библиотечную функцию" как "библиотечную функцию, которая не является встроенной функцией".

Есть ли что-то, что встроенная функция может выполнять, но функция библиотеки не может?

Да. Встроенная функция может выбирать, например, не для оценки своих аргументов:

int main() {
  int i = 0;
  __builtin_constant_p (++i); // checks whether ++i is a constant expression
                              // does not evaluate ++i
  return i; // returns 0
}

Это связано с тем, что встроенная функция может быть преобразована компилятором во что-то еще, что фактически не должно содержать никакого вызова функции.

Могу ли я написать библиотечную функцию, которая выполняет ту же задачу, что и сборка функции printf?

Существует некоторое встроенное знание printf, но по большей части это прекрасно выполнимо. Посмотрите, как использовать <stdarg.h>.

Как я могу указать тип входных параметров (% f, float или double)?

Вы должны доверять вызывающему абоненту, чтобы строка формата соответствовала остальным аргументам; вы не можете обнаружить что-то вроде передачи int, когда строка формата ожидает double. Но вам не нужно обрабатывать разницу между float и double, потому что невозможно передать float в printf: он будет преобразован в double (независимо от строки формата) до printf видит это. Требования printf были тщательно выполнены, чтобы избежать необходимости в какой-либо магии компилятора.

Машинные инструкции функций сборки GCC не хранятся в библиотеке, правильно?

Вызовы во встроенные функции преобразуются во время компиляции, но это преобразование может просто привести к вызову библиотечной функции с тем же именем.

Где они?

Если преобразование выполняется во время компиляции, машинных инструкций нет. Вызов преобразуется в другой код, и этот код затем скомпилируется для получения машинных инструкций. Если результатом является вызов функции библиотеки, машинные инструкции для этой библиотечной функции являются частью библиотеки.

Когда вы выполняете привязку, как вы можете контролировать, куда поместить эти коды функций сборки?

Я не понимаю, что вы имеете в виду здесь. Вызов встроенной функции преобразуется во время компиляции в другой код, а другой код затем компилируется как часть функции, содержащей вызов. Он будет помещен туда, где будет оставлен остальной код этой содержащей функции.

Почему иногда я могу отправлять сообщения об ошибках, например "undefined ссылка на __builtin_stdarg_start" при компоновке

Нет встроенной функции __builtin_stdarg_start, несмотря на префикс __builtin, поэтому это рассматривается как вызов функции библиотеки. И нет никакой библиотечной функции __builtin_stdarg_start, поэтому компоновщик обнаруживает это как ошибку.

Раньше была встроенная функция __builtin_stdarg_start, но она была удалена много лет назад, и код никогда не должен был ее использовать в первую очередь.

gcc -c main.c, nm показывает, что в main.o нет символа printf (только main (T) и puts (U)), почему?

Это потому, что printf существует как встроенная функция, так и как функция библиотеки. Встроенная функция обычно просто вызывает библиотечную функцию, но иногда это возможно сделать лучше, в том числе и в вашем примере. В этом случае встроенная функция printf может дать правильный результат без вызова библиотечной функции printf.

Ответ 2

Существует примерно два типа встроенных модулей: те, которые соответствуют стандартным библиотечным функциям (malloc, printf и strcpy, по умолчанию считаются встроенными), а те, t иметь аналог в стандартной библиотеке - подумайте о __builtin_expect, __builtin_prefetch и т.д.

Первый тип встроенных модулей позволяет компилятору испускать оптимизированный код вместо соответствующих вызовов. Зная внутреннюю семантику каждого из вызовов из стандартной библиотеки, компилятор может решить либо выдать вызов функции, которая находится в библиотеке, либо испустить на своем компьютере специально созданный фрагмент кода, чтобы исходная семантика сохраняются, и код работает лучше.

Второй тип встроенных модулей (также называемых "intrinsics" ) позволяет использовать трюки и оптимизации, которые вряд ли достижимы с помощью статической части кода, которая находится в библиотеке. Они могут перевести на то, чтобы дать подсказки CPU (__builtin_prefetch, __builtin_expect) или улучшить язык C с лучшей интроспекцией времени компиляции (__builtin_constant_p, __builtin_types_compatible_p) или предоставить более простой, независимый от платформы интерфейс для некоторые инструкции, относящиеся к архитектуре (__builtin_ffs, __builtin_popcount).