Не все знают, что в 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 в стандарте языка было всегда