Не все знают, что в GCC начиная с 5.0 стандартом по умолчанию сделали C99 (UPD: на самом деле GNU11, то есть C11 + GNU extensions)
Но это является источником радости. Надеюсь этот пост убережет кого-то из тех, кто пойдет по моим следам (перевод немаленького проекта на новый компилятор).
Итак вы берете некий код, который кажется не содержит никаких ошибок:
#include <stdlib.h>
#include <stdarg.h>
inline void debug(int n,...)
{
va_list ap;
va_start( ap, n );
va_end( ap );
}
int main(void)
{
debug(0, 1);
exit(0);
}
Компилируете его GCC 5.1 для x86 и получаете...
/tmp/ccSYThQj.o: In function `main':
repro.c: (.text+0x14): undefined reference to `debug'
ШТОА, думаете вы, как так, неужели ошибка в компиляторе. Как он выбросил функцию debug, если вот же она используется.
Но стоп.
Легко видеть, что с опцией --std=gnu90 все работает, что с опцией --std=c99 все не работает даже на старых версиях GCC где все работает по умолчанию и это значит... что семантика слова inline в C99 изменилась (6.7.4, особенно пункт 6)
Здравствуйте, Tilir, Вы писали:
T>Иначе компилятор действительно имеет право выкинуть эту штуку молча и игнорировать все имеющиеся ссылки (см. также bugzilla).
Правильно ли я понимаю, что конкретно это строка разрешает ему это делать?
Что-то я не уловил: это происходит, даже когда функции живут в одном файле? То есть компилятор вот так просто берёт и выкидывает функцию из того compilation unit'а, где она используется?
А можно пояснить логику происходящего? Полистал 6.7.4, ничерта не понял…
Здравствуйте, placement_new, Вы писали:
_>Правильно ли я понимаю, что конкретно это строка разрешает ему это делать?
_>"It is unspecified whether a call to the function uses the inline definition or the external definition." _>http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf
Да, но там самое важное выше:
"An inline definition provides an alternative to an external definition, which a translator may use to implement any call to the function in the same translation unit"
То есть вы предлагаете ему альтернативу которой он может воспользоваться чтобы проинлайнить функцию, объявленную где-то ещё. Если он этой альтернативой не воспользовался, он имеет право о ней забыть и сделать честный внешний вызов.
Здравствуйте, Tilir, Вы писали:
T>Не все знают, что в GCC начиная с 5.0 стандартом по умолчанию сделали C99. T>Но это является источником радости. Надеюсь этот пост убережет кого-то из тех, кто пойдет по моим следам (перевод немаленького проекта на новый компилятор).
Я давно пишу на C99 и привык использовать static inline, restrict-указатели и массивы в стеке. Но если так сложно портировать проект на C99, то почему не указать версию С, которая лучше всего подходит?
Здравствуйте, Tilir, Вы писали:
T>"An inline definition provides an alternative to an external definition, which a translator may use to implement any call to the function in the same translation unit" T>То есть вы предлагаете ему альтернативу которой он может воспользоваться чтобы проинлайнить функцию, объявленную где-то ещё. Если он этой альтернативой не воспользовался, он имеет право о ней забыть и сделать честный внешний вызов.
А компилятор не должен ли действовать как-то согласованно: или выкидывать-и-инлайнить, или выносить-и-вызывать (или выносить-но-всё-равно-инлайнить)?
Что за прикол такой, implementation-defined, сделать висячую ссылку?
К>А компилятор не должен ли действовать как-то согласованно: или выкидывать-и-инлайнить, или выносить-и-вызывать (или выносить-но-всё-равно-инлайнить)? К>Что за прикол такой, implementation-defined, сделать висячую ссылку?
А это просто неправильное использование inline. Хотя, действительно, с первого взгляда это может быть немного неочевидно.
Нет смысла внутри .c файла писать inline void foo() {...} если это единственная реализация foo. На возможность встраивания функции это никак не влияет (в документации же написано об игнорировании этого устаревшего смысла inline, в отличии от всяких __attribute__((always_inline)) ; как максимум наличие inline может случжить лишь рекомендацией компилятору обратить внимание на это место). На visibility наличие inline также не влияет. А других ролей у inline тем более нет.
Основное и правильное использование inline — это писать его в заголовочных файлах.
И компилятору при этом сообщается примерно следующее: «вот есть такая функция, возможно её стоит встроить, а так как для встраивания нужен ещё и исходный код, то вот он; а если решишь, что встраивание не даст пользы — вызывай функцию как обычно». Поэтому в .h файле пишется что-то вроде
inline void foo()
{
// реализация
}
И везде, где подключён файл .h можно вызывать функцию foo как обычно, но при этом компилятору доступна не только сигнатура, но и реализация, так что он может делать оптимизации её тела по месту.
Ну и как обычно, в одном из .c файлов даётся определение
extern inline void foo();
Причём, как видно, тут просто сообщается о том, какой объектный файл будет предоставляют функцию, а её код автоматически берётся из .h файла.
В общем, inline в c99 вполне рационально устроен. Главное его в .c не писать (без static или extern) — он там бессмысленнен и приводит к созданию такого рода тем.
Кстати говоря, в C++ есть механизмы, который работают точно также. Например,
class Bar {
static const int x = 4;
};
По стандарту недостаточно написать это в .h файле, но нужно дополнительно в одном из .cpp файлов сделать инстанциирование
const int Bar::x;
И причём, если этого не сделать, то код программы может как собраться (если компилятор встроит все места использования x), так и не собраться, если компилятору где-то захочется взять реальную переменную (а захотеться ему может внезапно по совершенно разным причинам).
То есть поведение практически совпадает с поведением inline в c99, только в одном случае это статические члены класса, а другом — функции. Остальное одинаково: «вот есть член/функция, встраивай если хочешь».
Что-же касается пассажа про разную реализацию inline и не-inline функций, то это разрешает также делать некоторые оптимизации. Например, можно локально в пределах одного .с отключить часть проверок внутри функции, если вызывающий код берёт на себя гарантии их выполнения. Или это позволяет узаконить факт, что основная реализация может быть собрана другим компилятором. В общем, ничего страшного, тем более, что обычно-то этого и не будет, а будет, как показано выше, просто указание компилятору взять и самому скопировать текст из inline функции в extern.
W>Ну и как обычно, в одном из .c файлов даётся определение
extern inline void foo();
Причём, как видно, тут просто сообщается о том, какой объектный файл будет предоставляют функцию, а её код автоматически берётся из .h файла.
Получается, определив inline функцию только в заголовочном файле, и не написать декларацию в .c файле — это, в принципе, не правильно?
На с++ такое распостраняется?
Всегда думал, что любое использование inline-функции (если компилятор решил ее не встраивать) просто приводит к созданию слабых ссылок на них, из которых линкер выберет только одну.
_>Получается, определив inline функцию только в заголовочном файле, и не написать декларацию в .c файле — это, в принципе, не правильно?
В смысле "декларацию в .c файле"? Может определение?
В С если у функции есть объявление (declaration), то её можно вызывать. Но для сборки программы где-то должно существовать определение (definition).
Если же у функции есть inline definition (это отдельный термин), например в виде записи в .h файле: inline int foo() { return 42; }), то ничего не меняется и где-то в программе по прежнему должно существовать определение (но при этом допустимо это определение записать в короткой форме extern int foo();, если этому предшествует inline definition).
_>На с++ такое распостраняется?
Именно на inline-функции — нет. Но в C++ есть полностью аналогичные заморочки с теми же статическими членами классов.
_>Всегда думал, что любое использование inline-функции (если компилятор решил ее не встраивать) просто приводит к созданию слабых ссылок на них, из которых линкер выберет только одну.
Это один из возможных способов реализации поведения С++. Правда там всё устроено чуть сложнее, так как, например, есть ещё требование по объединению static переменных для всех мест использования функции. Так что слабые секции создаются и в случае полного встраивания, и в случае внешнего вызова, но не для кода, а для данных.
Здравствуйте, Mystic, Вы писали:
M>Я давно пишу на C99 и привык использовать static inline, restrict-указатели и массивы в стеке. Но если так сложно портировать проект на C99, то почему не указать версию С, которая лучше всего подходит?
Дело не в сложности. Как только я осознал происходящее, я поправил все очень быстро. Дело в неожиданности. Вы пишете функцию, как обычно забивая на явный static (все программисты забивают на static пока не пригорит), потом вы вешаете на нее inline, чтобы компилятор ее при случае подставил. А он ее не подставляет а выкидывает. Это настолько не вяжется со всеми обычными практиками, что просто жесть.
Здравствуйте, Кодт, Вы писали:
К>А компилятор не должен ли действовать как-то согласованно: или выкидывать-и-инлайнить, или выносить-и-вызывать (или выносить-но-всё-равно-инлайнить)? К>Что за прикол такой, implementation-defined, сделать висячую ссылку?
Видимо считается, что extern inline это совершенно отдельный зверь. Нечто вроде опциональной заглушки, заводя которую, вы неявно обещаете компилятору что у вас где-то есть и настоящая функция. И если он не смог ее проинлайнить, он просто-таки обязан ее выкинуть, чтобы не было конфликта внешних имен.
Здравствуйте, Tilir, Вы писали:
T>Не все знают, что в GCC начиная с 5.0 стандартом по умолчанию сделали C99.
я смотрю что не все знают что сейчас 2015 год, а не 1999
за 15 лет уже новые программисты родились (sic!), а вы всё не можете прочитать что там такое написано в стандарте,
который содержит всего 500 страниц (а не 1300 как в С++)
Здравствуйте, Abyx, Вы писали:
A>за 15 лет уже новые программисты родились (sic!), а вы всё не можете прочитать что там такое написано в стандарте, A>который содержит всего 500 страниц (а не 1300 как в С++)
Я этот абзац читал дважды. Один раз в 99 стандарте, второй раз в 11-м, он там почти такой же. И ни разу ничего не зацепило взгляд.
Мне кажется, чтобы прочитать оттуда именно это, нужно очень хорошо знать, что именно собираешься прочитать.
Здравствуйте, watchmaker, Вы писали:
W>Основное и правильное использование inline — это писать его в заголовочных файлах.
В сях нет заголовочных файлов, в сях есть препроцессор.
W>И везде, где подключён файл .h можно вызывать функцию foo как обычно, но при этом компилятору доступна не только сигнатура, но и реализация, так что он может делать оптимизации её тела по месту. W>Ну и как обычно, в одном из .c файлов даётся определение
extern inline void foo();
Причём, как видно, тут просто сообщается о том, какой объектный файл будет предоставляют функцию, а её код автоматически берётся из .h файла. W>В общем, inline в c99 вполне рационально устроен. Главное его в .c не писать (без static или extern) — он там бессмысленнен и приводит к созданию такого рода тем.
Аа, догнал. То есть, inline — это объявление (даром что вместе с потрохами), а extern inline — это определение (работает как инстанцирование).
Просто взяли и выворотили смысл слова extern.
Понятно, ради чего это затевалось — чтобы уменьшить объёмы объектных файлов, выкинув оттуда дубликаты; и заодно избавить линкер от решения задач, что делать с дубликатами.
Но синтаксически... сломали совместимость.
W>Что-же касается пассажа про разную реализацию inline и не-inline функций, то это разрешает также делать некоторые оптимизации. Например, можно локально в пределах одного .с отключить часть проверок внутри функции, если вызывающий код берёт на себя гарантии их выполнения. Или это позволяет узаконить факт, что основная реализация может быть собрана другим компилятором. В общем, ничего страшного, тем более, что обычно-то этого и не будет, а будет, как показано выше, просто указание компилятору взять и самому скопировать текст из inline функции в extern.
То есть, у компилятора получаются уже три возможности:
— честно подставить инлайн, оптимизировав по максимуму
— сделать вызов оптимизированной функции (статической копии — т.е. тот же инлайн, но с jmp туда — jmp обратно)
— сделать вызов публичного символа, — возможно, реализованного в этой же секции кода, — но, естественно, не оптимизированного, потому что мало ли кто откуда ещё его дёрнет
?
Здравствуйте, jazzer, Вы писали:
J>Теоретически да, а практически у меня гцц в релизных билдах все собирает нормально, а дебаг-билдах требует инстанцирование.
Я недавно с gcc 4.4 кажется с таким сталкивался и релизе тоже.
Здравствуйте, Кодт, Вы писали:
К>В сях нет заголовочных файлов, в сях есть препроцессор.
Для педантов определение в терминах TU уже есть в стандарте. Ну не повторять же его?
К> То есть, inline — это объявление (даром что вместе с потрохами), а extern inline — это определение (работает как инстанцирование).
Практически да. Кстати, просто extern inline написать тоже достаточно.
К>Но синтаксически... сломали совместимость.
Сломали совместимость с чем? Такое поведение у inline в стандарте языка было всегда
Это в режиме gnu89 в gcc были добавлены разные расширения вроде лямбда-функций, поддержки конструкторов/деструкторов, альтернативной семантики inline, и прочие полезные и не очень вещи. Не очень справедливо жаловаться на сломанность изначально сломанного расширения. Лямбда-функции в c99 тоже не попали, но стоит ли ругать стандарт за сломанную совместимость?
Уж если в программе на эти расширения завязано много чего и нет средств приводить код к стандарту (хоть к c99, хоть в c89), то, как уже сказали
Здравствуйте, watchmaker, Вы писали:
К>>Но синтаксически... сломали совместимость. W>Сломали совместимость с чем? Такое поведение у inline в стандарте языка было всегда
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, watchmaker, Вы писали:
К>>>Но синтаксически... сломали совместимость. W>>Сломали совместимость с чем? Такое поведение у inline в стандарте языка было всегда
К>http://ideone.com/x4thLo — C89 — ok
ll.c:3:8: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘void’
inline void foo() { printf("foo\n"); }
^
А вот что думает clang-3.5 -std=c89:
ll.c:3:1: error: unknown type name 'inline'
inline void foo() { printf("foo\n"); }
^
Это уже не говоря о том, что есть и куда более важное несоответствие с c89:
gcc
ll.c:8:5: error: expected expression before ‘/’ token
// your code goes here
^
clang
ll.c:8:5: error: // comments are not allowed in this language
// your code goes here
^
К>Хочешь сказать, что инстанцирование &foo — это добрая воля и личная инициатива компилятора?
В с99 инстанциирования не будет по стандарту (и у компиляторов с этим нет проблем — не делать как-то всегда проще чем наоборот что-то делать).
В с89 такой код невалиден.
К>(Аналогичная ситуация со статическими членами-константами в С++ однозначно приводит к ошибке линковки).
Однозначно — слишком сильно сказано. Никто не гарантирует, что такая ситуация не приведёт. И, конечно, это нужно исправлять. Но достаточно часто не приводит из-за слишком умного оптимизатора, и забытое определение может быть обнаружено совсем не сразу.
Здравствуйте, jazzer, Вы писали:
W>>>Кстати говоря, в C++ есть механизмы, который работают точно также. Например,
W>>>class Bar {
W>>> static const int x = 4;
W>>>};
W>>>По стандарту недостаточно написать это в .h файле, но нужно дополнительно в одном из .cpp файлов сделать инстанциирование
const int Bar::x;
A>>только если "x" ODR-used. (http://ru.stackoverflow.com/a/419547/177684)
J>Теоретически да, а практически у меня гцц в релизных билдах все собирает нормально, а дебаг-билдах требует инстанцирование.
там по ссылке на RU.SO об этом и написано.
если есть код
int f(int& arg) { return arg; }
int main() {
return f(Bar::x);
}
то оптимизатор инлайнит код, и переменная перестает быть ODR-used.
а без оптимизаций f(Bar::x) остается, генерится код
extern dd Bar_x
...
main:
push offset Bar_x
call f
ret
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Abyx, Вы писали:
A>>лучше использовать онлайн компилятор в котором можно задать командную строку A>>http://coliru.stacked-crooked.com/a/2491064e67a98380
К>Ох щи! Оказывается, inline — это фича C99 и расширение GNU C89. То есть, все эти годы мы ходили по граблям...
Хорошо хоть MS используют два подчеркивания для расширений __inline.
Здравствуйте, Abyx, Вы писали:
A>Здравствуйте, Tilir, Вы писали:
T>>Не все знают, что в GCC начиная с 5.0 стандартом по умолчанию сделали C99.
A>я смотрю что не все знают что сейчас 2015 год, а не 1999 A>за 15 лет уже новые программисты родились (sic!), а вы всё не можете прочитать что там такое написано в стандарте, A>который содержит всего 500 страниц (а не 1300 как в С++)
Ну, я бы сказал, что это из области "программистского бессознательного". Я вот при "plain C" на автомате пишу static для всех локальных функций, но про тонкости стандартов Cxx vs Cyy действительно не задумывался (в отличие от C++), в первую очередь по причине идиомы "это ж plain C, старый язык, едва ли не legacy".
Здравствуйте, Tilir, Вы писали:
T>Hi,
T>Не все знают, что в GCC начиная с 5.0 стандартом по умолчанию сделали C99. T>Но это является источником радости. Надеюсь этот пост убережет кого-то из тех, кто пойдет по моим следам (перевод немаленького проекта на новый компилятор).
Благодарствую за граблеописание. Ещё раз убедился, что моя подсознательная нелюбовь к явному описанию inline — не спроста
T>Не все знают, что в GCC начиная с 5.0 стандартом по умолчанию сделали C99 (UPD: на самом деле GNU11, то есть C11 + GNU extensions) T>Но это является источником радости. Надеюсь этот пост убережет кого-то из тех, кто пойдет по моим следам (перевод немаленького проекта на новый компилятор).
T>Итак вы берете некий код, который кажется не содержит никаких ошибок:
Как-же будет компилиться ядро линукса?
Вот "стандартный" файл fs.h
MS>"static inline" — очень много. Будут переделывать?
Каюсь не обратил внимание на то, что static решает проблему
Сам постоянно пишу static inline и уж чуть было не бросился переписывать (пока только на 4.8.2 работаю)
A>int f(int& arg) { return arg; }
A>int main() {
A> return f(Bar::x);
A>}
A>
A>то оптимизатор инлайнит код, и переменная перестает быть ODR-used.
Ну нет. Переменная не перестаёт быть ODR-used. Это же формальное свойство — оно зависит только от текста программы, а не от используемого компилятора или его настроек оптимизации. И встраивание тут не исключение.
А собирается такая программа лишь из-за особенностей устройства линкера и компилятора. Раз по стандарту компилятор не должен следить за наличием определений для ODR-used переменных, то он и не следит. А linker игнорирует всё, на что нет внешних ссылок. Вместе две эти особенности и приводят к успешной сборке программы.
В общем, пропадает не свойство ODR-used, а ссылки внутри объектных файлов. Вот только первое — это про С++, а второе — лишь про особенности конкретной реализации. Сама же константа Bar::x как была ODR-used, так и осталась.