Встроенные функции C и неопределенная внешняя ошибка

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

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?)


person endolith    schedule 22.02.2012    source источник
comment
Непонятно, куда вы вставляете эти определения и когда вы получаете эти ошибки. Часть этого есть в заголовках? Вы объявляете в заголовке вперед и пытаетесь использовать его из другого модуля компиляции?   -  person Mat    schedule 22.02.2012
comment
О, определения функций и вызов функции находятся в одном и том же файле .c.   -  person endolith    schedule 22.02.2012
comment
Можете ли вы опубликовать sscce? (Ну, в данном случае, конечно, не компилируется.)   -  person Mat    schedule 22.02.2012
comment
@Mat: Не уверен, о чем ты просишь. Я определяю функцию, а затем вызываю ее внутри основной функции, которая находится в том же файле .c.   -  person endolith    schedule 22.02.2012
comment
Я просто пытаюсь заставить вас отредактировать свой вопрос, чтобы он содержал достаточно информации, чтобы точно воспроизвести вашу проблему, чтобы на нее можно было ответить правильно. Ваш вопрос как таковой не содержит информации в ваших комментариях. Если бы вы просто разместили полный файл C, который дает ошибку, которая у вас возникла, ваш вопрос был бы лучше. (Но я считаю, что некоторые из опубликованных ответов уже верны.)   -  person Mat    schedule 22.02.2012
comment
В C main() возвращает int. (кроме книг Херба Шильдта)   -  person wildplasser    schedule 23.02.2012
comment
@wildplasser: Тогда пример кода производителя для этого микроконтроллера неверен. В любом случае, не уверен, где возврат закончится.   -  person endolith    schedule 23.02.2012
comment
Это автономная реализация?   -  person wildplasser    schedule 23.02.2012
comment
@wildplasser: Не знаю, что это значит, но возможно. Это встроенный микроконтроллер. Основной код никогда не возвращается, а если он каким-то образом вернулся, ему не к чему возвращаться.   -  person endolith    schedule 23.02.2012


Ответы (3)


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

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

inline void do_something(void)
{
  blah
}

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

Если вы включаете объявление без 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, использование типов данных переменного размера, использование вычисляемого перехода, использование нелокального перехода и вложенных функций. Использование -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) в файл библиотеки. Определение в заголовочном файле приведет к тому, что большинство вызовов функции будут встроенными. Если какие-либо варианты использования функции останутся, они будут ссылаться на единственную копию в библиотеке.

person Tim Schaeffer    schedule 22.02.2012
comment
Во-первых, компилятор не всегда встраивает функции, помеченные как встроенные. Да, но не лучше ли позволить компилятору решать, а не заставлять его макросами? это заставляет компилятор выдавать код функции в каждой единице компиляции, которая его использует. Но если он не используется в этом файле, он не компилируется, не так ли? Здесь есть ограничения на размер скомпилированного кода. - person endolith; 22.02.2012
comment
«Да, но не лучше ли позволить компилятору решать, чем заставлять его макросами?» Я не понимаю этот вопрос. «Но если он не используется в этом файле, он не компилируется, не так ли?» Он компилируется, но машинный код не выдается; он по существу «выброшен». - person Tim Schaeffer; 10.07.2012

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

inline void do_something(void)
{
  blah
}

и в одной единице компиляции (также известной как .c) вы помещаете своего рода "экземпляр"

void do_something(void);

без inline.

person Jens Gustedt    schedule 22.02.2012
comment
Согласованный. Кажется, здесь есть аналогичный вопрос. stackoverflow.com/questions/6312597/ - person Gargi Srinivas; 22.02.2012

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

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

person Community    schedule 22.02.2012