О чем должен обратиться третий x
:
#include static char x = '1'; int main(void) { char x = '2'; { extern char x; printf("%c\n", x); } }
Это возникло в этом ответе и:
x
extern char x
относится к первому x
, а «1» печатается. C 2018 6.2.2 4 говорит:
Для идентификатора, объявленного с помощью внешнего спецификатора classа хранения в области видимости, в которой видна предварительная декларация этого идентификатора, если предыдущее объявление указывает внутреннюю или внешнюю связь, связь идентификатора с последующим объявлением совпадает с привязкой указанных в предыдущем заявлении. Если ни одно предварительное объявление не отображается, или если в предыдущем объявлении не указана ссылка, то идентификатор имеет внешнюю привязку.
Поскольку существует два предшествующих объявления x
, условие каждого из следующих «if» предложений истинно, первое для первого предшествующего объявления и второе для второго предшествующего объявления:
Поведение Клана здесь согласуется с использованием первого предложения, так что третий x
имеет внутреннюю связь и относится к тому же объекту, что и первый x
. Поведение GCC здесь согласуется с использованием второго предложения, так что третий x
имеет внешнюю связь и конфликтует с первым x
, который имеет внутреннюю связь.
Предоставляет ли стандарт C нам возможность решить, какой из них должен иметь место?
Третья декларация extern char x
должна объявить x
с внешней связью на основе C 2018 6.2.2 4, в которой говорится:
Для идентификатора, объявленного с помощью внешнего спецификатора classа хранения в области видимости, в которой видна предварительная декларация этого идентификатора, если предыдущее объявление указывает внутреннюю или внешнюю связь, связь идентификатора с последующим объявлением совпадает с привязкой указанных в предыдущем заявлении. Если ни одно предварительное объявление не отображается, или если в предыдущем объявлении не указана ссылка, то идентификатор имеет внешнюю привязку.
В объявлении extern char x
первое объявление x
не видно, поскольку оно было скрыто вторым объявлением. Следовательно, он не может претендовать на «предварительное объявление этого идентификатора». Второе объявление x
является видимым, поэтому оно является «предшествующим заявлением» для целей вышеуказанного абзаца.
Тогда последнее предложение должно контролировать: в предыдущем объявлении не указывается ссылка (6.2.2 6, идентификатор области блока без extern
не имеет привязки), поэтому третий x имеет внешнюю связь.
Тогда нарушение 6.2.2 7 нарушается, поскольку первый x
имеет внутреннюю связь, а третий x
имеет внешнюю связь:
Если внутри единицы перевода появляется один и тот же идентификатор с внутренней и внешней связью, поведение не определено.
Поскольку никакое синтаксическое правило или ограничение не нарушено, реализация стандарта не требуется стандартом для сообщения о диагностике. Поскольку поведение не определено, оно может сделать что угодно, в том числе принять этот код и сделать третий x
ссылкой на тот же объект, что и первый x
. Поэтому ни поведение Кланг, ни поведение GCC не нарушают стандарт в этом отношении. Однако, поскольку нарушение 6.2.27, может быть предпочтительным, и его отсутствие может рассматриваться как дефект Клана.
(Отклик на Paul Ogilvie и TC за то, что он сообщил мне об этом со своими комментариями.)