Что делает extern inline?

93

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

Я думаю, что это static inlineделает то же самое (может или не может быть встроенным), но не будет создавать связываемый объектный код при встроенном (поскольку ни один другой модуль не может ссылаться на него).

Где extern inlineвписывается в картину?

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

Есть ли различия между C ++ и C?

Есть ли различия между разными поставщиками и версиями компиляторов?

драгошт
источник

Ответы:

129

в K&R C или C89 инлайн не был частью языка. Многие компиляторы реализовали его как расширение, но не было определенной семантики относительно того, как это работает. GCC был одним из первых , чтобы осуществить встраивание и введен inline, static inlineи extern inlineконструкции; большинство компиляторов до C99 обычно следуют его примеру.

GNU89:

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

C99 (или GNU99):

  • inline: как GNU89 "extern inline"; никакая внешне видимая функция не испускается, но одна может быть вызвана и поэтому должна существовать
  • extern inline: как GNU89 "inline": испускается видимый извне код, поэтому его может использовать не более одной единицы перевода.
  • static inline: вроде GNU89 "статический встроенный". Это единственный переносимый между gnu89 и c99

C ++:

Функция, которая является встроенной где угодно, должна быть встроена везде с одним и тем же определением. Компилятор / компоновщик отсортирует несколько экземпляров символа. Нет определения static inlineили extern inline, хотя они есть во многих компиляторах (как правило, следуя модели gnu89).

пуецк
источник
2
В классическом классическом C слово «inline» не было ключевым словом; его можно было использовать как имя переменной. Это применимо к C89 и предварительному стандарту (K&R) C.
Джонатан Леффлер
Кажется, ты прав. Исправлена. Я думал, что это было зарезервировано как ключевое слово в C89 (хотя и не в K&R), но, похоже, я неправильно запомнил
puetzk
Я хотел бы добавить, что для Microsoft Visual C ++ есть ключевое слово __forceinline, которое заставит вашу функцию встроиться. Очевидно, что это расширение, специфичное для компилятора, только для VC ++.
untitled8468927
Есть ли разница между C99 "extern inline" и вообще без спецификаторов?
Джо Со
Семантически нет; точно так же, как не встроенная функция, extern inlineподчиняется правилу одного определения, и это определение. Но если эвристика оптимизации, определяемая реализацией, следует рекомендации использовать inlineключевое слово в качестве предложения, что «вызовы функции должны быть как можно более быстрыми» (ISO 9899: 1999 §6.7.4 (5), extern inlineсчитается
puetzk
31

Я считаю, что вы неправильно поняли __FILE__ и __LINE__ на основании этого утверждения:

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

Есть несколько этапов компиляции, и предварительная обработка - первая. __FILE__ и __LINE__ заменяются на этом этапе. Итак, к тому времени, когда компилятор сможет рассмотреть функцию для встраивания, они уже заменены.

Дон Нойфельд
источник
14

Похоже, вы пытаетесь написать что-то вроде этого:

inline void printLocation()
{
  cout <<"You're at " __FILE__ ", line number" __LINE__;
}

{
...
  printLocation();
...
  printLocation();
...
  printLocation();

и надеемся, что каждый раз вы будете получать разные значения. Как говорит Дон, вы этого не сделаете, потому что __FILE__ и __LINE__ реализуются препроцессором, а встроенные - компилятором. Итак, откуда бы вы ни вызывали printLocation, вы получите тот же результат.

Только способ , которым Вы можете получить эту работу, чтобы сделать printLocation макрос. (Да, я знаю...)

#define PRINT_LOCATION  {cout <<"You're at " __FILE__ ", line number" __LINE__}

...
  PRINT_LOCATION;
...
  PRINT_LOCATION;
...
Родди
источник
18
Обычный трюк заключается в том, что макрос PRINT_LOCATION вызывает функцию printLocation, передавая FILE и LINE в качестве параметров. Это может привести к лучшему поведению отладчика / редактора / и т.д., когда тело функции нетривиально.
Стив Джессоп,
@Roddy Посмотрите на мое решение - ваше расширение, но более полное и расширяемое.
enthusiasticgeek
@SteveJessop Что-то вроде того, что я перечислил в решении ниже?
enthusiasticgeek
3

Ситуация с inline, static inline и extern inline сложна, не в последнюю очередь потому, что gcc и C99 определяют несколько разные значения для своего поведения (и, предположительно, C ++ тоже). Вы можете найти полезную и подробную информацию о том, что они делают в C здесь .

Саймон Ховард
источник
2

Здесь вы выбираете макросы, а не встроенные функции. Редкий случай, когда макросы преобладают над встроенными функциями. Попробуйте следующее: я написал этот код "МАКРОМАГИИ", и он должен работать! Протестировано на gcc / g ++ Ubuntu 10.04

//(c) 2012 enthusiasticgeek (LOGGING example for StackOverflow)

#ifdef __cplusplus

#include <cstdio>
#include <cstring>

#else

#include <stdio.h>
#include <string.h>

#endif

//=========== MACRO MAGIC BEGINS ============

//Trim full file path
#define __SFILE__ (strrchr(__FILE__,'/') ? strrchr(__FILE__,'/')+1 : __FILE__ )

#define STRINGIFY_N(x) #x
#define TOSTRING_N(x) STRINGIFY_N(x)
#define _LINE (TOSTRING_N(__LINE__))

#define LOG(x, s...) printf("(%s:%s:%s)"  x "\n" , __SFILE__, __func__, _LINE, ## s);

//=========== MACRO MAGIC ENDS ============

int main (int argc, char** argv) {

  LOG("Greetings StackOverflow! - from enthusiasticgeek\n");

  return 0;
}

Для нескольких файлов определите эти макросы в отдельном файле заголовка, включая их в каждом файле c / cc / cxx / cpp. По возможности предпочитайте встроенные функции или константные идентификаторы (в зависимости от случая) макросам.

энтузиазм
источник
2

Вместо ответа «что он делает?» Я отвечаю: «как мне заставить его делать то, что я хочу?» Существует 5 видов встраивания, все из которых доступны в GNU C89, стандартном C99 и C ++:

всегда в строке, если не указан адрес

Добавьте __attribute__((always_inline))к любому объявлению, а затем используйте один из следующих случаев, чтобы обработать возможность его адреса.

Вы, вероятно, никогда не должны использовать это, если вам не нужна его семантика (например, чтобы влиять на сборку определенным образом или использовать alloca). Компилятор обычно лучше вас знает, стоит ли оно того.

встроенный и испускает слабый символ (например, C ++, он же «просто заставь его работать»)

__attribute__((weak))
void foo(void);
inline void foo(void) { ... }

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

встроенный, но никогда не испускать никаких символов (оставляя внешние ссылки)

__attribute__((gnu_inline))
extern inline void foo(void) { ... }

испускать всегда (для одного TU, чтобы разрешить предыдущее)

Подсказанная версия испускает слабый символ в C ++, но сильный символ на любом диалекте C:

void foo(void);
inline void foo(void) { ... }

Или вы можете сделать это без подсказки, которая испускает сильный символ на обоих языках:

void foo(void) { ... }

Как правило, вы знаете, на каком языке находится ваш ЕП, когда предоставляете определения, и, вероятно, не нуждаетесь во встраивании.

inline и emit в каждом TU

static inline void foo(void) { ... }

Для всех из них, кроме staticодного, вы можете добавить void foo(void)объявление выше. Это помогает с «лучшей практикой» писать чистые заголовки, а затем #includeвставлять отдельный файл со встроенными определениями. Затем, если вы используете встроенные строки в стиле C, #defineнекоторые макросы по- разному в одном выделенном TU для предоставления автономных определений.

Не забывайте, может extern "C"ли заголовок использоваться как в C, так и в C ++!

o11c
источник
Отсутствует: как насчет MSVC? У него есть некоторые расширения диалекта C89, но я никогда не использую MSVC и не знаю, как запустить его nmэквивалент.
o11c 08