2014-10-08 1 views
4

Я интересно, если C сниппет ниже, в котором определение f не удается повторить, что f имеет static связи, является правильным:Согласованность связи между декларацией и определением

static int f(int); 

int f(int x) { return x; } 

Clang не выделяет какой-либо предупреждая об этом. Я прочитал раздел 6.7.1 стандарта C11, не найдя ответа на мой вопрос.

Можно представить себе больше вопросов в том же духе, например, 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. 

ответ

6

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

  • Первая декларация определяет связь.
  • 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; } 

Это, с другой стороны, это неопределенное поведение (см 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, более поздняя декларация может скрыть предыдущее объявление.

+3

Я не понимаю, почему второй фрагмент требует диагностики. В противоположность тому, что цитирует @BlueMoon, кажется, что это «единственный» - это UB. –

+0

@JensGustedt: Спасибо, мне было интересно, как UB, упомянутый в ответе Blue Moon, должен произойти вообще, если это было нарушение ограничений, и это ответ. Я правильно понимаю, что, как правило, отказ от компиляции на UB (в заявлении) означает, что компилятор должен иметь возможность определить, что данный код доступен, но это не применимо здесь, потому что мы имеем дело с (file-scope) объявления? – mafso

+1

Да, вот это код, который не выполнен, поэтому достижимость не имеет большого смысла :) UB здесь означает, что вся программа имеет UB с самого начала. Вероятно, в основном потому, что ссылка на идентификаторы, возможно, пошла не так. Тем не менее, учитывая, что, поскольку UB является немного хромым, это легко обнаружить во время компиляции, так что, по-моему, нарушение ограничений будет в порядке. –

5

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

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

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

С11, 6.2.1 Области применения идентификаторов

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

+0

Извините, я перемещаю «принятый» тег на другой ответ, который лучше соответствует поведению Клана. Я благодарен вам за то, что вы быстро указали, что это объяснение можно найти в 6.2.2. –

+0

np, это ваш выбор. Я согласен, что mafso лучше, так как он покрывает дополнительную разницу, когда дело касается порядка декларирования (и +1 к mafso!). –

Смежные вопросы