Соответствие связи между декларацией и определением

Мне интересно, верен ли приведенный ниже фрагмент C, в котором определение f не повторяет, что f связано с static:

static int f(int);

int f(int x) { return x; }

Clang не выдает никаких предупреждений об этом. Прочитал пункт 6.7.1 стандарта С11, не найдя ответа на свой вопрос.

Можно представить и другие вопросы в том же ключе, например, t1.c и t2.c ниже, и было бы хорошо, если бы ответ был достаточно общим, чтобы применить его к некоторым из них, но меня действительно беспокоит только первый пример выше.

~ $ cat t1.c
static int f(int);

int f(int);

int f(int x) { return x; }
~ $ clang -c -std=c99 -pedantic t1.c
~ $ nm t1.o
warning: /Applications/Xcode.app/…/bin/nm: no name list
~ $ cat t2.c
int f(int);

static int f(int);

int f(int x) { return x; }
~ $ clang -c -std=c99 -pedantic t2.c
t2.c:3:12: error: static declaration of 'f' follows non-static declaration
static int f(int);
           ^
t2.c:1:5: note: previous declaration is here
int f(int);
    ^
1 error generated.

person Pascal Cuoq    schedule 08.10.2014    source источник


Ответы (2)


Правила связывания немного сбивают с толку, и они различаются для функций и объектов. Вкратце, правила таковы:

  • Первое объявление определяет связь.
  • static означает внутреннюю связь.
  • extern означает связь как уже объявленную, если она не объявлена, внешнюю.
  • Если ни один из них не указан, это то же самое, что и extern для функций, и внешняя связь для идентификаторов объектов (с определением в той же единице перевода).

Итак, это действительно:

static int f(int); // Linkage of f is internal.

int f(int); // Same as next line.

extern int f(int); // Linkage as declared before, thus internal.

int f(int x) { return x; }

Это, с другой стороны, поведение undefined (см. C11 (n1570) 6.2.2 p7):

int f(int); // Same as if extern was given, no declaration visible,
            // so linkage is external.

static int f(int); // UB, already declared with external linkage.

int f(int x) { return x; } // Would be fine if either of the above
                           // declarations was removed.

Большая часть этого описана в C11 6.2.2. Из проекта N1570:

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

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

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

30) Объявление функции может содержать спецификатор класса хранения static только в том случае, если оно находится в области действия файла; см. 6.7.1.
31) Как указано в 6.2.1, более позднее объявление может скрывать предыдущее объявление.

person mafso    schedule 08.10.2014
comment
Я не понимаю, почему второй фрагмент требует диагностики. Вопреки тому, что цитирует @BlueMoon, кажется, что это только UB. - person Jens Gustedt; 08.10.2014
comment
@JensGustedt: Спасибо, мне было интересно, как UB, упомянутый в ответе Blue Moon, вообще должен произойти, если это было нарушение ограничения, и это ответ. Правильно ли я понимаю, что обычно отказ от компиляции в UB (в операторе) означает, что компилятор должен иметь возможность определить, что рассматриваемый код достижим, но здесь это не применимо, потому что мы имеем дело с ( файловой области) объявления? - person mafso; 08.10.2014
comment
Да, здесь это код, который не выполняется, поэтому достижимость не имеет особого смысла :) UB здесь означает, что вся программа имеет UB с самого начала. Вероятно, в основном потому, что привязка идентификаторов могла пойти не так. Тем не менее, имея это, поскольку UB немного хромает, это легко обнаружить во время компиляции, поэтому, я думаю, нарушение ограничения будет в порядке. - person Jens Gustedt; 08.10.2014
comment
@JensGustedt: Спасибо. обоснование C89 имеет смысл скажем об этом, возможно, причина UB заключается в том, чтобы разрешить расширения/меньше изменений в существующих реализациях, не требуя диагностики для реализаций, где это имеет какое-то четко определенное значение (каким бы это ни было), и полагаясь на то, что разработчики достаточно сложны, чтобы прервать компиляция, если это не так, вместо форматирования жесткого диска... - person mafso; 08.10.2014

Согласно C11, 6.2.2, 7, все они являются неопределенными поведениями.

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

Функция также является идентификатором, а функция по умолчанию (без какого-либо квалификатора, такого как static) имеет внешнюю связь.

C11, 6.2.1 Области действия идентификаторов

1 Идентификатор может обозначать объект; функция; тег или элемент структуры, объединения или перечисления; имя typedef; название ярлыка; имя макроса; или параметр макроса. Один и тот же идентификатор может обозначать разные сущности в разных точках программы. Член перечисления называется константой перечисления. Имена макросов и параметры макросов далее здесь не рассматриваются, потому что до семантической фазы преобразования программы любые вхождения имен макросов в исходном файле заменяются последовательностями токенов предварительной обработки, которые составляют их макроопределения.

person P.P    schedule 08.10.2014
comment
Извините, я перемещаю тег «принято» на другой ответ, который лучше соответствует поведению Clang. Я по-прежнему благодарен вам за то, что вы быстро указали, что объяснение можно найти в 6.2.2. - person Pascal Cuoq; 08.10.2014
comment
нп, это ваш выбор. Я согласен, что mafso лучше, поскольку он покрывает дополнительную разницу, когда порядок объявления имеет значение (и +1 к mafso!). - person P.P; 08.10.2014