(Обратите внимание, что изменения в коде в вопросе, кажется, делают части этого ответа более не совсем правильными.)
Per 6.2.2 Связи идентификаторов , пункт 4 стандарт C :
Для идентификатора, объявленного с помощью спецификатора класса хранения extern
в области видимости, в которой видна предыдущая декларация этого идентификатора, если предыдущая декларацияуказывает внутреннюю или внешнюю связь, связь идентификатора в последующем объявлении такая же, как связь, указанная в предыдущем объявлении.
Итак, в ваших первых двух случаяхвнутренний extern int a;
имеет предыдущее объявление - глобальное int a;
в вашем первом случае или extern int a;
во втором - поэтому объявление extern int a;
относится к глобальному.
Для третьего случая:остальная часть параграфа 4 актуальна:
Если предыдущее объявление не видно или если в предыдущем объявлении не указана связь, то идентификатор имеет внешнюю связь.
Также в параграфе 6 говорится:
Следующие идентификаторы не имеют связи: идентификатор, объявленный как что-либо, кроме объекта или функции;идентификатор, объявленный как параметр функции; идентификатор области блока для объекта, объявленного без спецификатора класса хранения extern
.
Таким образом, объявление в вашем третьем случае ссылается на extern int a;
Однако нигде не определено фактическое int a;
.И поскольку extern int a;
в вашем третьем примере появляется в области видимости блока, а фактическое определение объекта int a;
в другом месте отсутствует, ваша программа не может установить связь.
Per 6.9.2 Внешнийопределения объекта , абзац 2 гласит:
Объявление идентификатора для объекта, который имеет область действия файла без инициализатора и без спецификатора класса хранения или сспецификатор класса хранения static
представляет собой предварительное определение.Если модуль перевода содержит одно или несколько предварительных определений для идентификатора, а модуль перевода не содержит внешнего определения для этого идентификатора, то поведение точно такое, как если бы модуль перевода содержал объявление области файла для этого идентификатора с составным типом какконца единицы перевода, с инициализатором, равным 0.
Таким образом, объявление block-scope extern int a;
вашего третьего случая не квалифицируется как предварительное определение.