Встроенные функции C и «неопределенная внешняя» ошибка - PullRequest
6 голосов
/ 22 февраля 2012

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

void do_something(void)
{
  blah;
}

void main(void)
{
  do_something();
}

, но если я определю их как встроенные:

inline void do_something(void)
{
  blah;
}

void main(void)
{
  do_something();
}

, то будет написано «Ошибка: неопределенный внешний».Что это значит?Взяв удар в темноте, я попытался

static inline void do_something(void)
{
  blah;
}

void main(void)
{
  do_something();
}

и больше никаких ошибок.Определение функции и вызов функции находятся в одном файле .c.

Может кто-нибудь объяснить, почему один работает, а другой нет?

(Второй связанный с этим вопрос: куда поместить встроенные функции, если я хочу использовать их в нескольких файлах .c?)

Ответы [ 3 ]

10 голосов
/ 22 февраля 2012

Во-первых, компилятор не всегда выполняет встроенные функции, помеченные как inline; Например, если вы отключите все оптимизации, они, вероятно, не будут встроены в них.

Когда вы определяете встроенную функцию

inline void do_something(void)
{
  blah
}

и используйте эту функцию, даже в одном и том же файле, вызов этой функции разрешается компоновщиком, а не компилятором, поскольку это косвенно "extern". Но одно только это определение не дает внешнего определения функции.

Если включить декларацию без inline

void do_something(void);

в файле C, который может видеть определение inline, компилятор предоставит внешнее определение функции, и ошибка должна исчезнуть.

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

Лучше всего поместить встроенную функцию в файл заголовка и объявить их static inline. Это устраняет необходимость во внешнем определении, поэтому решает проблему компоновщика. Однако это приводит к тому, что компилятор генерирует код для функции в каждом модуле компиляции, который его использует, что может привести к раздуванию кода. Но так как функция встроенная, она в любом случае, вероятно, мала, поэтому обычно это не проблема.

Другой вариант: определить как extern inline в заголовке, а в одном файле C указать extern объявление без модификатора inline.

Руководство gcc объясняет это так:

Объявляя встроенную функцию, вы можете указать GCC совершать вызовы эта функция быстрее. Одним из способов достижения этой цели GCC является интеграция код этой функции в код для ее вызывающих. Это делает выполнение быстрее за счет устранения накладных расходов на вызовы функций; в Кроме того, если любое из фактических значений аргумента является постоянным, их известные значения могут позволить упрощения во время компиляции, чтобы не весь код встроенной функции должен быть включен. Эффект на размер кода менее предсказуем; код объекта может быть больше или меньше с функцией встраивания, в зависимости от конкретного случая. Вы можете также направьте GCC, чтобы попытаться интегрировать все «достаточно простые» функции в их звонящие с опцией -finline-functions.

GCC реализует три разные семантики объявления функции в соответствии. Один доступен с -std=gnu89 или -fgnu89-inline или когда атрибут gnu_inline присутствует во всех встроенных объявлениях, другой когда -std=c99, -std=c1x, -std=gnu99 или -std=gnu1x (без -fgnu89-inline), а третий используется при компиляции C ++.

Чтобы объявить функцию встроенной, используйте ключевое слово inline в ее объявление, как это:

 static inline int
 inc (int *a)
 {
   return (*a)++;
 }

Если вы пишете файл заголовка для включения в программы ISO C90, напишите __inline__ вместо inline.

Три типа встраивания ведут себя одинаково в двух важных случаях: когда ключевое слово inline используется в функции static, например, пример выше, и когда функция впервые объявлена ​​без использования inline ключевое слово и затем определяется с помощью inline, например:

 extern int inc (int *a);
 inline int
 inc (int *a)
 {
   return (*a)++;
 }

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

Когда функция является одновременно встроенной и static, если все вызовы Функция интегрирована в вызывающего, и адрес функции никогда не используется, то собственный код ассемблера функции никогда не будет ссылки. В этом случае GCC фактически не выводит код ассемблера для функции, если вы не укажете опцию -fkeep-inline-functions. Некоторые звонки не могут быть интегрированы для различных причины (в частности, вызовы, предшествующие определению функции не могут быть интегрированы, и рекурсивные вызовы в определение). Если есть неинтегрированный вызов, то функцияскомпилирован в ассемблерный код как обычно.Функция также должна быть скомпилирована как обычно, если программа ссылается на свой адрес, потому что он не может быть встроенным.

Обратите внимание, что некоторые использования в определении функции могут сделать ее непригодной для встроенной замены.Среди этих применений: использование varargs, использование alloca, использование типов данных переменного размера, использование вычисленного goto, использование нелокального goto и вложенных функций.Использование -Winline предупредит, когда функция, помеченная inline, не может быть заменена, и даст причину сбоя.

В соответствии с требованиями ISO C ++, GCC рассматривает функции-члены, определенные в теле класса.быть помечены как встроенные, даже если они явно не объявлены с ключевым словом inline.Вы можете переопределить это с помощью -fno-default-inline.

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

 /* Prototype.  */
 inline void foo (const char) __attribute__((always_inline));

Остатокэтого раздела относится к встраиванию в GNU C90.

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

Если в определении функции указать inline и extern, то определение используется только длявстраивание.Ни в коем случае функция не компилируется сама по себе, даже если вы явно ссылаетесь на ее адрес.Такой адрес становится внешней ссылкой, как если бы вы только объявили функцию и не определили ее.

Эта комбинация inline и extern почти не влияет на макрос.Способ его использования - поместить определение функции в заголовочный файл с этими ключевыми словами и поместить еще одну копию определения (без inline и extern) в библиотечный файл.Определение в заголовочном файле приведет к тому, что большинство вызовов функции будут встроенными.Если какое-либо использование функции останется, они будут ссылаться на одну копию в библиотеке.

1 голос
/ 22 февраля 2012

Для inline функций для работы с C99 (они пришли только на языке) вам нужно дать определение в заголовочном файле

inline void do_something(void)
{
  blah
}

и в one В модуле компиляции (он же .c) вы помещаете какую-то «реализацию»

void do_something(void);

без inline.

0 голосов
/ 22 февраля 2012

Вы должны поместить их в заголовочный файл, если хотите использовать их из нескольких файлов.

И для ошибки компоновщика: объявление функции по умолчанию подразумевает, что она "extern", но, поскольку она встроена, компоновщик может найти сгенерированный компилятором заглушку символа, отсюда и ошибку.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...