Феерия с inline в C99 и GCC 5.1
От: Tilir Россия http://tilir.livejournal.com
Дата: 03.07.15 12:37
Оценка: 18 (3)
Hi,

Не все знают, что в 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)

Теперь вы должны писать либо вот так:

static inline void debug(const char *msg,...)
{
/* ... */
}


либо добавлять extern definition

#include <stdlib.h>
#include <stdarg.h>

void debug(const char *msg,...);

inline void debug(const char *msg,...)
{
  va_list ap;
  va_start( ap, msg );
  va_end( ap );
}

int main(void)
{
  debug(0, 1);
  exit(0);
}


Иначе компилятор действительно имеет право выкинуть эту штуку молча и игнорировать все имеющиеся ссылки (см. также bugzilla).

Вот такие дела.

P.S. Может это баян давно и я один до сих пор в танке, но поиском не нашлось.

---
With best regards, Konstantin
Отредактировано 06.07.2015 10:34 Tilir . Предыдущая версия . Еще …
Отредактировано 03.07.2015 12:39 Tilir . Предыдущая версия .
Отредактировано 03.07.2015 12:38 Tilir . Предыдущая версия .
Re: Феерия с inline в C99 и GCC 5.1
От: placement_new  
Дата: 03.07.15 13:45
Оценка:
Здравствуйте, Tilir, Вы писали:

T>Иначе компилятор действительно имеет право выкинуть эту штуку молча и игнорировать все имеющиеся ссылки (см. также bugzilla).


Правильно ли я понимаю, что конкретно это строка разрешает ему это делать?

"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
Отредактировано 03.07.2015 13:47 placement_new . Предыдущая версия . Еще …
Отредактировано 03.07.2015 13:46 placement_new . Предыдущая версия .
Отредактировано 03.07.2015 13:46 placement_new . Предыдущая версия .
Re: Феерия с inline в C99 и GCC 5.1
От: CaptainFlint Россия http://flint-inc.ru/
Дата: 03.07.15 13:55
Оценка:
Здравствуйте, Tilir, Вы писали:

Что-то я не уловил: это происходит, даже когда функции живут в одном файле? То есть компилятор вот так просто берёт и выкидывает функцию из того compilation unit'а, где она используется?
А можно пояснить логику происходящего? Полистал 6.7.4, ничерта не понял…
Почему же, ё-моё, ты нигде не пишешь «ё»?
Re[2]: Феерия с inline в C99 и GCC 5.1
От: placement_new  
Дата: 03.07.15 13:59
Оценка: 7 (1)
Здравствуйте, CaptainFlint, Вы писали:

http://stackoverflow.com/questions/6312597/is-inline-without-static-or-extern-ever-useful-in-c99/6312854#6312854
Re[2]: Феерия с inline в C99 и GCC 5.1
От: Tilir Россия http://tilir.livejournal.com
Дата: 03.07.15 14:47
Оценка:
Здравствуйте, 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"

То есть вы предлагаете ему альтернативу которой он может воспользоваться чтобы проинлайнить функцию, объявленную где-то ещё. Если он этой альтернативой не воспользовался, он имеет право о ней забыть и сделать честный внешний вызов.
Re: Феерия с inline в C99 и GCC 5.1
От: Mystic Украина http://mystic2000.newmail.ru
Дата: 03.07.15 14:51
Оценка: +2
Здравствуйте, Tilir, Вы писали:

T>Не все знают, что в GCC начиная с 5.0 стандартом по умолчанию сделали C99.

T>Но это является источником радости. Надеюсь этот пост убережет кого-то из тех, кто пойдет по моим следам (перевод немаленького проекта на новый компилятор).

Я давно пишу на C99 и привык использовать static inline, restrict-указатели и массивы в стеке. Но если так сложно портировать проект на C99, то почему не указать версию С, которая лучше всего подходит?
Re[3]: Феерия с inline в C99 и GCC 5.1
От: Кодт Россия  
Дата: 03.07.15 17:50
Оценка:
Здравствуйте, 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, сделать висячую ссылку?
http://files.rsdn.org/4783/catsmiley.gif Перекуём баги на фичи!
Re[4]: Феерия с inline в C99 и GCC 5.1
От: watchmaker  
Дата: 03.07.15 18:58
Оценка: 144 (7)
Здравствуйте, Кодт, Вы писали:


К>А компилятор не должен ли действовать как-то согласованно: или выкидывать-и-инлайнить, или выносить-и-вызывать (или выносить-но-всё-равно-инлайнить)?

К>Что за прикол такой, 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.
Re[5]: Феерия с inline в C99 и GCC 5.1
От: placement_new  
Дата: 03.07.15 20:22
Оценка:
Здравствуйте, watchmaker, Вы писали:


W>Ну и как обычно, в одном из .c файлов даётся определение
extern inline void foo();
Причём, как видно, тут просто сообщается о том, какой объектный файл будет предоставляют функцию, а её код автоматически берётся из .h файла.


Получается, определив inline функцию только в заголовочном файле, и не написать декларацию в .c файле — это, в принципе, не правильно?
На с++ такое распостраняется?
Всегда думал, что любое использование inline-функции (если компилятор решил ее не встраивать) просто приводит к созданию слабых ссылок на них, из которых линкер выберет только одну.
Отредактировано 03.07.2015 20:28 placement_new . Предыдущая версия . Еще …
Отредактировано 03.07.2015 20:28 placement_new . Предыдущая версия .
Re[6]: Феерия с inline в C99 и GCC 5.1
От: watchmaker  
Дата: 03.07.15 22:08
Оценка:
Здравствуйте, placement_new, Вы писали:


_>Получается, определив inline функцию только в заголовочном файле, и не написать декларацию в .c файле — это, в принципе, не правильно?

В смысле "декларацию в .c файле"? Может определение?

В С если у функции есть объявление (declaration), то её можно вызывать. Но для сборки программы где-то должно существовать определение (definition).
Если же у функции есть inline definition (это отдельный термин), например в виде записи в .h файле: inline int foo() { return 42; }), то ничего не меняется и где-то в программе по прежнему должно существовать определение (но при этом допустимо это определение записать в короткой форме extern int foo();, если этому предшествует inline definition).

_>На с++ такое распостраняется?

Именно на inline-функции — нет. Но в C++ есть полностью аналогичные заморочки с теми же статическими членами классов.

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

Это один из возможных способов реализации поведения С++. Правда там всё устроено чуть сложнее, так как, например, есть ещё требование по объединению static переменных для всех мест использования функции. Так что слабые секции создаются и в случае полного встраивания, и в случае внешнего вызова, но не для кода, а для данных.
Re[2]: Феерия с inline в C99 и GCC 5.1
От: Tilir Россия http://tilir.livejournal.com
Дата: 04.07.15 10:09
Оценка:
Здравствуйте, Mystic, Вы писали:

M>Я давно пишу на C99 и привык использовать static inline, restrict-указатели и массивы в стеке. Но если так сложно портировать проект на C99, то почему не указать версию С, которая лучше всего подходит?


Дело не в сложности. Как только я осознал происходящее, я поправил все очень быстро. Дело в неожиданности. Вы пишете функцию, как обычно забивая на явный static (все программисты забивают на static пока не пригорит), потом вы вешаете на нее inline, чтобы компилятор ее при случае подставил. А он ее не подставляет а выкидывает. Это настолько не вяжется со всеми обычными практиками, что просто жесть.
Re[4]: Феерия с inline в C99 и GCC 5.1
От: Tilir Россия http://tilir.livejournal.com
Дата: 04.07.15 10:13
Оценка:
Здравствуйте, Кодт, Вы писали:

К>А компилятор не должен ли действовать как-то согласованно: или выкидывать-и-инлайнить, или выносить-и-вызывать (или выносить-но-всё-равно-инлайнить)?

К>Что за прикол такой, implementation-defined, сделать висячую ссылку?

Видимо считается, что extern inline это совершенно отдельный зверь. Нечто вроде опциональной заглушки, заводя которую, вы неявно обещаете компилятору что у вас где-то есть и настоящая функция. И если он не смог ее проинлайнить, он просто-таки обязан ее выкинуть, чтобы не было конфликта внешних имен.
Re: Феерия с inline в C99 и GCC 5.1
От: Abyx Россия  
Дата: 04.07.15 18:04
Оценка:
Здравствуйте, Tilir, Вы писали:

T>Не все знают, что в GCC начиная с 5.0 стандартом по умолчанию сделали C99.


я смотрю что не все знают что сейчас 2015 год, а не 1999
за 15 лет уже новые программисты родились (sic!), а вы всё не можете прочитать что там такое написано в стандарте,
который содержит всего 500 страниц (а не 1300 как в С++)
In Zen We Trust
Re[5]: Феерия с inline в C99 и GCC 5.1
От: Abyx Россия  
Дата: 04.07.15 18:15
Оценка: +1
Здравствуйте, watchmaker, Вы писали:

W>Кстати говоря, в C++ есть механизмы, который работают точно также. Например,
W>class Bar {
W>   static const int x = 4;
W>};

W>По стандарту недостаточно написать это в .h файле, но нужно дополнительно в одном из .cpp файлов сделать инстанциирование
const int Bar::x;


только если "x" ODR-used. (http://ru.stackoverflow.com/a/419547/177684)
In Zen We Trust
Re[2]: Феерия с inline в C99 и GCC 5.1
От: Tilir Россия http://tilir.livejournal.com
Дата: 04.07.15 19:07
Оценка: 1 (1) +2
Здравствуйте, Abyx, Вы писали:

A>за 15 лет уже новые программисты родились (sic!), а вы всё не можете прочитать что там такое написано в стандарте,

A>который содержит всего 500 страниц (а не 1300 как в С++)

Я этот абзац читал дважды. Один раз в 99 стандарте, второй раз в 11-м, он там почти такой же. И ни разу ничего не зацепило взгляд.
Мне кажется, чтобы прочитать оттуда именно это, нужно очень хорошо знать, что именно собираешься прочитать.
Re[5]: Феерия с inline в C99 и GCC 5.1
От: Кодт Россия  
Дата: 04.07.15 23:25
Оценка:
Здравствуйте, 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 обратно)
— сделать вызов публичного символа, — возможно, реализованного в этой же секции кода, — но, естественно, не оптимизированного, потому что мало ли кто откуда ещё его дёрнет
?
http://files.rsdn.org/4783/catsmiley.gif Перекуём баги на фичи!
Re[6]: Феерия с inline в C99 и GCC 5.1
От: jazzer Россия Skype: enerjazzer
Дата: 05.07.15 06:04
Оценка:
Здравствуйте, Abyx, Вы писали:

A>Здравствуйте, watchmaker, Вы писали:


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)


Теоретически да, а практически у меня гцц в релизных билдах все собирает нормально, а дебаг-билдах требует инстанцирование.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[7]: Феерия с inline в C99 и GCC 5.1
От: placement_new  
Дата: 05.07.15 08:14
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Теоретически да, а практически у меня гцц в релизных билдах все собирает нормально, а дебаг-билдах требует инстанцирование.


Я недавно с gcc 4.4 кажется с таким сталкивался и релизе тоже.
Re[6]: Феерия с inline в C99 и GCC 5.1
От: watchmaker  
Дата: 05.07.15 09:47
Оценка:
Здравствуйте, Кодт, Вы писали:

К>В сях нет заголовочных файлов, в сях есть препроцессор.


Для педантов определение в терминах TU уже есть в стандарте. Ну не повторять же его?

К> То есть, inline — это объявление (даром что вместе с потрохами), а extern inline — это определение (работает как инстанцирование).

Практически да. Кстати, просто extern inline написать тоже достаточно.


К>Но синтаксически... сломали совместимость.

Сломали совместимость с чем? Такое поведение у inline в стандарте языка было всегда
Это в режиме gnu89 в gcc были добавлены разные расширения вроде лямбда-функций, поддержки конструкторов/деструкторов, альтернативной семантики inline, и прочие полезные и не очень вещи. Не очень справедливо жаловаться на сломанность изначально сломанного расширения. Лямбда-функции в c99 тоже не попали, но стоит ли ругать стандарт за сломанную совместимость?

Уж если в программе на эти расширения завязано много чего и нет средств приводить код к стандарту (хоть к c99, хоть в c89), то, как уже сказали
Автор: Mystic
Дата: 03.07.15
, почему бы просто не продолжить использовать gnu89 в старых частях кода?
Re[7]: Феерия с inline в C99 и GCC 5.1
От: Кодт Россия  
Дата: 05.07.15 10:22
Оценка:
Здравствуйте, watchmaker, Вы писали:

К>>Но синтаксически... сломали совместимость.

W>Сломали совместимость с чем? Такое поведение у inline в стандарте языка было всегда

http://ideone.com/x4thLo — C89 — ok
http://ideone.com/BjDw9c — C99 — undefined reference
#include <stdio.h>

inline void foo() { printf("foo\n"); }

int main(void) {
    void (*f)() = &foo;
    f();
    // your code goes here
    return 0;
}

Хочешь сказать, что инстанцирование &foo — это добрая воля и личная инициатива компилятора?

(Аналогичная ситуация со статическими членами-константами в С++ однозначно приводит к ошибке линковки).
http://files.rsdn.org/4783/catsmiley.gif Перекуём баги на фичи!
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.