Несовместимые спецификаторы классов хранения для функций - PullRequest
6 голосов
/ 10 июня 2019

У меня есть

static void static_func(void);
void static_func(void) { }
static inline void static_inline_func(void);
void static_inline_func(void) { }

#if 0 //error
    void extern_func(void){}
    static void extern_func(void);
#endif

int main()
{
    static_func();
    static_inline_func();
}

Я ожидал предупреждений или ошибок, но ни gcc, ни clang ничего не делают, даже с -Wall -Wextra -pedantic.

Является ли это совместимым C?Почему или почему нет?

Ответы [ 3 ]

8 голосов
/ 10 июня 2019

Относительно:

static void static_func(void);
void static_func(void) { }

Для первой строки C 2018 6.2.2 3:

Если объявление идентификатора области файла для объекта или функции содержит спецификатор класса хранения static, идентификатор имеет внутреннюю связь.

Для второй строки 6.2.2 5 говорит:

Если объявление идентификатора для функции не имеет спецификатора класса хранения, его связь определяется точно так, как если бы оно было объявлено с помощью спецификатора класса хранения extern.…

поэтому мы ссылаемся на параграф о том, когда extern указано, 6.2.2 4 (выделение добавлено):

Для идентификатора, объявленного с помощью спецификатора класса хранения extern в области видимости, в которой видно предыдущее объявление этого идентификатора, , если в предыдущем объявлении указана внутренняя или внешняя связь, связь идентификатор в более позднем объявлении такой же, как связь, указанная в предыдущем объявлении.…

Таким образом, поскольку предыдущее объявление является видимым, void static_funct(void) эквивалентно static void static_funct(void).

(Обратите внимание, что 6.2.2 5 отличается для объектов; после части, указанной выше, он продолжает: «Если объявление идентификатора для объекта имеет область файла и не имеет спецификатора класса хранения, его связь является внешней. Таким образом, static int x; int x; создает конфликт для объекта x, тогда как static int f(void); int f(void); не создает конфликт для функции f.)

Относительно:

static inline void static_inline_func(void);
void static_inline_func(void) { }

inline - это спецификатор функции, а не спецификатор класса хранения, и 6.7.4 6 говорит нам:

Функция, объявленная со спецификатором функции inline, является встроенной функцией .…

Как мы видели выше, void static_inline_func(void) { } все еще имеет внутреннюю связь из-за предварительного объявления. 6.7.4 7 довольно слабо относится к требованиям к встроенным функциям с внутренней связью:

Любая функция с внутренней связью может быть встроенной функцией.…

Если бы функция не была объявлена ​​с static, есть дополнительные ограничения в 6.7.4 7:

Для функции с внешним связыванием применяются следующие ограничения: Если функция объявлена ​​со спецификатором функции inline, то она также должна быть определена в той же единице перевода. Если все объявления области файла для функции в модуле перевода включают в себя спецификатор функции inline без extern, то определение в этом модуле перевода является встроенным определением. Встроенное определение не предоставляет внешнего определения для функции и не запрещает внешнее определение в другой единице перевода. Встроенное определение предоставляет альтернативу внешнему определению, которое переводчик может использовать для реализации любого вызова функции в той же единице перевода.…

(Непонятно, где заканчивается текст, охватываемый «применяются следующие ограничения».)

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

3 голосов
/ 10 июня 2019

Я почти уверен, что это действительно C.

Следующие стандартные ссылки взяты из C99, но черновик C17 содержит точно такой же текст.

6.2.2 (Связи идентификаторов) говорит:

Для идентификатора, объявленного с помощью спецификатора класса хранения extern в области видимости, в которой видно предыдущее объявление этого идентификатора, если в предыдущем объявлении указана внутренняя или внешняя связь, связь идентификатора вболее поздняя декларация такая же, как и связь, указанная в предыдущей декларации.Если никакое предыдущее объявление не видно, или если в предыдущем объявлении не указана связь, то идентификатор имеет внешнюю связь.

Если объявление идентификатора для функции не имеет спецификатора класса храненияего связь определяется точно так, как если бы она была объявлена ​​с помощью спецификатора класса хранения extern.

Таким образом: extern не всегда означает внешнюю связь.Если в текущей области видна существующая декларация с внутренней связью (static), то это то, что имеет приоритет.

Однако, 6.11 (Будущие направления языка) также говорит:

6.11.2 Связи идентификаторов

  1. Объявление идентификатора с внутренней связью в области видимости без спецификатора класса хранения static является устаревшей функцией.

Так что, хотя это допустимая особенность C, вам, вероятно, не стоит полагаться на нее в новом коде.

2 голосов
/ 10 июня 2019

Цитата из 6.2.2 Linkagesof identifiers p4.

Для идентификатора, объявленного с помощью спецификатора класса хранения extern в области видимости, в которой видно предыдущее объявление этого идентификатора, 23)если в предыдущем объявлении указана внутренняя или внешняя связь, то связь идентификатора в последующем объявлении совпадает с связью, указанной в предыдущем заявлении.

Если предшествующее объявление не видно или если в предыдущем объявлении указанонет связи, то у идентификатора есть внешняя связь

Итак, в этих 2 случаях 2-е объявление правильное, а связь статическая (2-е объявление внешнее по умолчанию):

static void static_func(void);
void static_func(void) { }

static inline void static_inline_func(void);
void static_inline_func(void) { }

Цитата из p7, та же глава:

Если внутри единицы перевода появляется один и тот же идентификатор как с внутренней, так и с внешней связью, поведение не определено

Итак, декларировать это как неопределенное поведение.

#if 0 //error
    void extern_func(void){}
    static void extern_func(void);
#endif
...