Определяет ли inline внутреннюю связь?

Я пытаюсь внедрить встроенную функцию. Как я думал, это должно работать:

//a.cpp
inline void f(int) {}
//b.cpp
extern void f(int);
int main() { f(4); }

Но получаю ошибку ссылки. Затем, прочитав это ("1) должно быть объявлено inline в каждой единице перевода."). Что я пробовал:

//a.cpp
inline void f(int) {}
//b.cpp
extern inline void f(int);
int main() { f(4); }

Все еще получаю ошибку ссылки. Но теперь, пытаясь что-то, что я не знаю, что я делаю:

//a.cpp
extern inline void f(int) {}
//b.cpp
extern inline void f(int);
int main() { f(4); }

Оно работает. Что тут происходит? Прежде чем добавлять extern ко всему, f в a.cpp имела внутреннюю связь?

Я использую MSVC 2017 (v141) с /permissive- и /std:c++17


person João Paulo    schedule 28.05.2019    source источник
comment
Было бы полезно упомянуть компилятор, который вы используете (отметьте его)   -  person Michael Chourdakis    schedule 28.05.2019
comment
extern inline void f(int) {} выглядит немного странно. Вы дали определение функции, которая помечена как определенная в другом месте.   -  person alter igel    schedule 28.05.2019
comment
The definition of an inline function or variable (since C++17) must be present in the translation unit where it is accessed. Функция inline void f(int) {} уже имеет внешнюю связь и должна быть определена в b.cpp. Функция уже имеет внешнюю связь. static inline void f(int){} имеет внутр.   -  person KamilCuk    schedule 28.05.2019
comment
FWIW, вы можете просто определить встроенную функцию в заголовочном файле, который включается во все файлы, которые в ней нуждаются.   -  person NathanOliver    schedule 28.05.2019
comment
@KamilCuk Это и декларация, и определение.   -  person HolyBlackCat    schedule 28.05.2019
comment
@KamilCuk В дополнение к предыдущему: все определения являются объявлениями.   -  person eerorika    schedule 28.05.2019
comment
extern inline void f(int); позволит вам получить только адрес f, но не вызовет f() напрямую. Для этого вам по-прежнему необходимо предоставить определение в каждой единице перевода.   -  person rustyx    schedule 28.05.2019
comment
Я думаю, что встроенный и внешний являются взаимоисключающими. Вы говорите компилятору поместить функцию в единицу перевода (inline) и говорите ему пойти и поискать ее в другой единице перевода (extern).   -  person Mirko    schedule 29.05.2019
comment
Подумайте, что функция inline может быть определена более чем в одной единице перевода (вот что это значит). Какой из них extern выберет?   -  person Mirko    schedule 29.05.2019
comment
В любом случае, чего вы пытаетесь достичь, используя extern и inline одновременно?   -  person Mirko    schedule 29.05.2019
comment
Я пробую что угодно, просто интересно, как это работает. И я согласен, что это не имеет смысла.   -  person João Paulo    schedule 29.05.2019
comment
Вам может быть интересен мой ответ здесь (наряду с другими ответами на этот вопрос), но обратите внимание, что я знаю только информацию, относящуюся к GCC. , а не информацию, специфичную для MSVC (если кто-нибудь может добавить ее, это было бы здорово).   -  person o11c    schedule 29.05.2019


Ответы (1)


Я пытаюсь внедрить встроенную функцию.

Нет причин использовать extern с функцией. См. длительность хранения — привязка. Функции имеют внешнюю связь по умолчанию; чтобы не иметь внешней связи, необходимо сделать что-то особенное (например, поместить его в анонимное пространство имен или объявить его static). Таким образом, обычное использование встроенной функции уже демонстрирует внешнюю связь без использования ключевого слова extern.

Как я думал, это должно работать:

//a.cpp
inline void f(int) {}
//b.cpp
extern void f(int);
int main() { f(4); }

Затем, прочитав это ("1) Он должен быть объявлен inline в каждую единицу перевода.").

Эта ссылка верна, но поищите еще немного, где говорится: "Определение встроенной функции [...] должно присутствовать в единице перевода, где к ней осуществляется доступ [...]." В вашем примере есть объявление f в b.cpp, но не определение. Если вы собираетесь вызывать f из b.cpp, вам нужно полное определение в этой единице перевода, например:

inline void f(int) {}

(Это тот же код, что и в a.cpp.) Если вы опустите фигурные скобки, то у вас будет объявление, но не определение, что сделает незаконным вызов f из этой единицы перевода.

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


Что означает "inline"?

С точки зрения компилятора ключевое слово inline (почти) ничего не значит. Это просто флаг в определении функции, который передается в объектный код, чтобы компоновщик его увидел. Компилятор обрабатывает функцию так же, как любую другую функцию. Функция может вызываться обычным образом, или ее вызовы могут быть встроенными, как и любая другая функция.

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

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

И это все. Ключевое слово inline в основном означает, что определение функции находится в заголовочном файле. Это говорит компоновщику не жаловаться, когда это определение появляется в нескольких единицах перевода, потому что это ожидается.

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

person JaMiT    schedule 28.05.2019
comment
Функция может вызываться обычным образом, или ее вызовы могут быть встроенными, как и любая другая функция.. Это не совсем правда. По крайней мере, для Clang встроенный спецификатор — при применении к функции — регулирует порог стоимости встраивания и повышает вероятность того, что функция будет встроена. - person Léo Lam; 25.06.2021