Extern с не работает

Нет подсказки, почему extern не работает

Я просто тестирую, как работает extern (используя MSVC), и я не могу заставить его работать, независимо от того, что я делаю:

Это приводит к ошибке связывания, хотя я и определил ее в заголовке. Я не хочу включать заголовок, потому что, как я его читаю, он говорит, что может быть в другой единицы перевода и не нужно включать. Неужели я ошибаюсь в том, как я его читаю, или что-то неправильно написал? Если я включаю заголовок, он работает, как и должно, даже без объявления extern.

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

То, что «extern» возникает, поскольку это просто «объявление» переменной без «определения» ее. «extern» означает «будет определен в какой-либо другой единицы перевода».

Таким образом, обычный способ:

Файлы заголовков обычно не являются единицами перевода.

Поместите определение в файл Test.cpp .

Поместите основную функцию в файл Main.cpp .

Компиляция и связь.

Для MS используйте cl.exe в качестве компилятора вместо g++ .

Сначала спросите себя, почему вы используете такую вещь, как extern. И тогда вы поймете, как его использовать. Представьте, что вы хотели бы, чтобы фрагмент кода вызывался в разных программах. Один раз из консольного приложения и один раз из внешней программы.

Таким образом, вы будете писать Test.cpp.

Теперь вы можете использовать его в консольном приложении, используя функцию main(), и как только вы создадите его вместе с внешней программой, вы можете использовать его там тоже.

Оба будут использовать значение externalint 10 и до тех пор, пока вы обновите эту тестовую библиотеку в обоих проектах, например, через git. У вас будет одинаковое поведение как в консольном приложении, так и во внешней программе.

Где Test.h вы спрашиваете? Там вы идете:

Test.cpp будет скомпилирован отдельно от вашего Main.cpp, но вы хотите использовать переменные и функции из вашего Test.cpp. Как компилятор знает, какие функции существуют?

Вы сообщите ему. Вы просто создаете интерфейс → Test.h. И затем вы включите его в свой Main.cpp

Но что, если у вас есть больше.cpp файлов, заинтересованных в этом интерфейсе? Тогда он будет дублироваться, и он не будет компилироваться. Потому что вы не можете инициализировать одну и ту же переменную дважды.

К счастью, вы можете объявить об этом и объявить, что инициализация будет выполнена в одном из файлов.cpp.

Источник

Понятия не имею, почему не работает extern

Я просто проверяю, как работает extern (используя MSVC), и я не могу заставить его работать, независимо от того, что я делаю:

Это приводит к ошибке компоновки, хотя я определил ее в заголовке. Я не хочу включать заголовок, потому что способ, которым я его читаю, говорит, что он может быть в другой единице перевода и не должен быть включен. Я ошибаюсь в том, как я читаю, или я что-то написал неправильно? Если я включаю заголовок, он работает, как и должно, даже без объявления extern.

Решение

Заголовочные файлы обычно не являются единицами перевода, но должны быть включены ими. Вот почему заголовочные файлы обычно не «определяют» переменные, поскольку это может привести к множественным ошибкам определения, когда заголовочный файл включается различными единицами перевода (таким образом, переопределение переменной снова и снова).

Вот где «extern» вступает в действие, поскольку это просто для «объявления» переменной без ее «определения». «extern» означает «будет определен в некоторой другой единице перевода».

Итак, обычный способ:

Другие решения

Заголовочные файлы обычно не являются единицами перевода.

Поместите определение в Test.cpp файл.

Поместите свою основную функцию в Main.cpp файл.

Компиляция и ссылка.

Для MS используйте cl.exe в качестве компилятора вместо g++ ,

Сначала спросите себя, почему вы используете такую ​​вещь, как extern. И тогда вы поймете, как его использовать.
Представьте, что вы хотите, чтобы фрагмент кода вызывался в разных программах.
Один раз из консольного приложения и один раз из внешней программы.

Итак, вы напишите Test.cpp.

Теперь вы можете использовать его в консольном приложении, используя функцию main (), и как только вы соберете его вместе с внешней программой, вы сможете использовать его и там.

Оба будут использовать значение externalint, равное 10, и до тех пор, пока вы обновляете эту тестовую библиотеку в обоих проектах, например, через git. У вас будет одинаковое поведение как в консольном приложении, так и во внешней программе.

Где находится Test.h, спросите вы?
Там вы идете:

Test.cpp будет скомпилирован отдельно от вашего Main.cpp, но вы хотели бы использовать переменные и функции из вашего Test.cpp. Как компилятор узнает, какие функции существуют?

Вы дадите ему знать.
Вы просто создаете интерфейс -> Test.h.
И тогда вы включите его в свой Main.cpp

Но что, если у вас есть больше .cpp файлов, заинтересованных в этом интерфейсе?
Тогда он будет повторяться и не будет компилироваться. Потому что вы не можете инициализировать одну и ту же переменную дважды.

К счастью, вы можете объявить это и объявить, что инициализация будет выполнена в одном из файлов .cpp.

Источник

Переменная extern и проблема с функцией extern между файлами cpp

** Это большой проект, и это просто абстракция **

Я НЕ в состоянии построить при включении глобальной переменной и глобальной функции в fileC.cpp.
Но это работает с fileB.cpp просто отлично.

Я пробовал несколько комбинаций, чтобы я мог использовать функцию и переменную через 3 файла .

Решение

Я согласен с Csaba Toth в комментариях. Это беспорядок, и вы должны правильно его почистить. Убедите людей, которые привлекли вас к проекту, что стоит потратить время, чтобы переписать его с правильными заголовками и исходными файлами. Это сэкономит им массу средств на разработку и поддержку и поможет выявить ошибки до того, как клиент их обнаружит.

Тем не менее, вам все еще нужно иметь дело с экстерьером.

Ваш выбор 1 очень плохой. .cpp никогда не должен включать другой .cpp. Это просто чудо, что вы не получаете тонны «уже определенных» ошибок.

Вариант 2 определяет другое int статического ish в файле B. Это будет отличное от int в файле C, и они могут иметь разные значения.

Что вам нужно сделать, это объявить файл C int как extern , Вы можете сделать это либо в заголовке, либо в cpp. Хитрость заключается в том, чтобы убедиться, что он создан только в одном месте.

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

Это говорит компилятору, что будет int с именем dog к тому времени, когда программа готова к запуску. Затем компоновщик будет искать единственный-единственный экземпляр этой переменной — в настоящее время находящийся в fileC.cpp — и указывать все на одно и то же место.

Ваш выбор 3 давал вам проблемы, потому что у вас есть две глобальные переменные с одинаковым именем. С использованием extern решу это.

Как правило, хорошей структурой кодирования будет:

Заголовочные файлы

  • Убедитесь, что есть обертки, либо #ifdef MYFILE_H или же #pragma once если это поддерживается, убедитесь, что .h никогда не может быть включен более одного раза.
  • Размещайте только объявления функций — у вас должно быть очень мало реализаций, если только вы не отчаянно пытаетесь сделать это встроенным (и хорошие компиляторы в настоящее время могут прекрасно оптимизировать самостоятельно для большинства ситуаций). Вы должны убедиться, что все реализуемые вами функции являются внутренними только для этого файла и не зависят от других файлов.
  • Вы можете ссылаться на другие глобальные переменные файла, используя extern ,

Cpp файлы

Самым чистым является сопоставление реализаций из вашего заголовка, хотя компиляторы и компоновщики позволят вам реализовать что угодно где угодно. Основное правило, которое необходимо соблюдать, заключается в том, что каждая функция и каждый внешний элемент должны быть реализованы один и только один раз во всем вашем коде. Должно быть только одно место с функцией myFunc и должно быть только одно место, которое объявляет int dog вместо extern int dog , Обе функции объявления и extern Переменные говорят компилятору: «Не беспокойся об этом, я позабочусь, чтобы это было сделано», и тогда компоновщик ищет его среди всех твоих объектов.

Я предполагаю, из-за того, что вы не упомянули никаких ошибок в вашем выборе 1 и 2, что вы дошли до компиляции, но никогда не запускали компоновщик. Следующее мое предположение заключается в том, что это система Unix, так как Visual Studio выполняет оба шага одновременно. В этом случае должны быть некоторые make система, которая позаботится о строительстве, или что-то. У вас должны были быть ошибки со всеми тремя вариантами.

Источник

Extern в C/C++

Оговорка — собирал все под VS2015 и под него же с Clang’ом для убедительности. В первую очередь интересует актуальная для них информация, но любая другая — тоже, только укажите о чем идет речь

В C по дефолту имеет тип хранения — extern(если нет ининциализации), а в C++ — нет, но и не static.
Вопрос #1: Есть ли этому причины, кроме усиления контроля над ошибками?

В C несколько одноименных extern’ов в разных файлах скомпилируются без инициализации(кстати, всегда ли так было?)
C++ же требует явно задать место, где переменная будет храниться
Вопрос #2: тот же, что и 1

Вопрос #3: Как extern применяется к структурам? (в первую очередь в си, но вдруг есть различия с C++ — интересно услышать)
Пример(на си): создаю 3 одноименных структуры в разных компилируемых файлах — 2 одинаковых(8байт не считая выравнивания) и 1 8байт+1024байт
Если все заданы без модификатора(extern), они, очевидно, ссылкаются на третью, самую большую структуру, если же третью структуру задать с extern(как я понимаю, явно указать, что она выделяется не тут), она будет по размеру равна первым двум
Всегда ли это работает по такому принципу?

P.S. Да, стандарты то я почитать могу, вот только почитать и правильно понять в этом случае — две большие разницы, к тому же, здесь может быть речь идет не только о стандарте

Что означает extern «C» или extern «C++»?
Например такой код, extern «C» void f(); Или C++ вместо C. Что это означает и где это применяется.

extern
В общем, вынужден заниматься разработкой проекта с модульным процедурным подходом(про ООП ни.

extern
Собственно как правильно пользоваться такой штукой? 1. Где нужно использовать: 1) только в.

extern
Всем привет, читал сейчас библиотеку SDL и там встретил такой вот код: extern «C» < //.

Решение

Где контекст? Где находится это объявление? Надо полагать, на уровне файла? (В С++ — на уровне namespace)?

Нет. С точки зрения конечного результата никакой разницы между С и С++ здесь нет.

В С это — tentative definition для переменной с external linkage. Если далее по коду не встретится объявление с инициализатором, то по достижении конца это единицы трансляции tentative definition будет рассматриваться, как определение с инициализатором 0 (т.е. int a = 0; )

В С++ нет tentative definition. В С++ это сразу рассматривается как определение переменной с external linkage. Переменная будет проинициализирована 0.

То есть когда компиляция единицы трансляции завершена, эффект от int a; полностью идентичен и в С и в С++. Какие-либо различия между С и С++ в данном случае полностью локализованы внутри единицы трансляции и внешнему миру никак не видны.

Нет. В самом языке такого не было никогда. В С такое множественное определение запрещено, абсолютно точно так же как и в С++. Это всегда было ошибкой и в С и в С++.

Однако в С такое поведение относится к разряду «популярных расширений» языка и даже упоминается в качестве такового в стандарте. (По секрету заметим, что такое расширение является неприятным побочным эффектом внутренней реализации механизма tentative definitions.)

Нет. Ничего подобного в С нет. Язык требует, чтобы все объявления одного и того же объекта имели совместимые (compatible) типы. Для структур это требование выливается во взаимно-однозначное соответствие (и совместимость) объявлений их полей. Поэтому объявление одного и того же объекта с «разносоставленными» struct-типами в разных единицах трансляции — это ошибка в С.

В С++ вообще открыто требуется, чтобы одноименные класс-типы с внешним связыванием имели идентичные определения.

На самом деле есть. В Си глобальная неинициализированная переменная является COMMON’ом, а в Си++ — нет

COMMON — это метка, определение которой имеет право находиться в нескольких модулях. Линкер их просто сошьёт одну с другой. Если они разных размеров (это скорее всего некорректно с точки зрения стандарта), то в качестве результирующего размера возьмётся бОльший. Такое поведение скорее всего унаследовалось из Фортран-77

Добавлено через 7 минут
Для полноты картины:

Для Си «aaa» попало в секцию .common, «bbb» — в секцию .bss (могло попасть и в .data, но в виде оптимизаций попало в .bss)

В моем ответе речь идет именно о том, что с точки зрения формальных определений этих языков никакой разницы между С и С++ нет. А куда там эти символы попали в конкретных реализациях — это не более чем особенности (багофичи) этих реализаций.

У этих деталей реализации могут быть исторические корни и попытки сохранения обратной совместимости. Никто не сомневается в том, что компиляторы С делают это специально, именно для того, чтобы поддержать возможность делать такие множественные определения, как сделаны в вашем примере. Как я уже говорил выше, спецификация С явным образом упоминает возможность такого множественного определения, как распространенное расширение языка. Ваши примеры лишь демонстрируют одну из реализаций этого расширения.

Но это не меняет того факта, что язык С формально считает ваш код ошибочным, ибо ваш код содержит множественные внешние определения одного и того же идентификатора.

Считает, считает. Вот соответствующие места в стандарте (подчеркивание мое)

6.9 External definitions

5 [. ] If an identifier declared with external linkage is used in an expression (other than as part of the operand of a sizeof or _Alignof operator whose result is an integer constant), somewhere in the entire program there shall be exactly one external definition for the identifier; otherwise, there shall be no more than one.

6.9.2 External object definitions

1 If the declaration of an identifier for an object has file scope and an initializer, the declaration is an external definition for the identifier.

2 A declaration of an identifier for an object that has file scope without an initializer, and without a storage-class specifier or with the storage-class specifier static, constitutes a tentative definition. If a translation unit contains one or more tentative definitions for an identifier, and the translation unit contains no external definition for that identifier, then the behavior is exactly as if the translation unit contains a file scope declaration of that identifier, with the composite type as of the end of the translation unit, with an initializer equal to 0.

А есть соответствующее место из стандарта C89? На моей памяти это было ещё до 99 года

Добавлено через 1 минуту
На моей памяти построение компиляторами в секции .common взялось именно из требований языка Си

Добавлено через 2 минуты
Ещё попробую покопаться в почте на работе, возможно, эта переписка сохранилась

Переписка была 2001 года. Пришлось много помучиться, пока всё нашёл
и выковырял, а потому уже не было сил, чтобы очень глубоко во всё это
вникнуть. Тем не менее, после двух прочтений я каких-то явных противоречий
в написанном не увидел

Начало проблемы я так и не нашёл, но вроде бы речь шла о коммерческих
исходниках, в которых засветилась подобная конструкция. Далее обсуждение
перешло на задачу 132.ijpeg из пакета spec95, в котором имела место быть
указанная ситуация. А потом уже перетекло в обсуждение стандарта

Письмо 1. Привожу выдержку из стандарта, т.к. на неё ссылаются далее

6.9.2 External object definitions

Semantics
1 If the declaration of an identifier for an object has file scope and an
initializer, the declaration is an external definition for the identifier.

2 A declaration of an identifier for an object that has file scope without an
initializer, and without a storage-class specifier or with the storage-class
specifier static, constitutes a tentative definition. If a translation unit
contains one or more tentative definitions for an identifier, and the
translation unit contains no external definition for that identifier, then
the behavior is exactly as if the translation unit contains a file scope
declaration of that identifier, with the composite type as of the end of the
translation unit, with an initializer equal to 0.

3 If the declaration of an identifier for an object is a tentative definition
and has internal linkage, the declared type shall not be an incomplete type.

4 EXAMPLE 1
int i1 = 1; // definition, external linkage
static int i2 = 2; // definition, internal linkage
extern int i3 = 3; // definition, external linkage
int i4; // tentative definition, external linkage
static int i5; // tentative definition, internal linkage

int i1; // valid tentative definition, refers to previous
int i2; // 6.2.2 renders undefined, linkage disagreement
int i3; // valid tentative definition, refers to previous
int i4; // valid tentative definition, refers to previous
int i5; // 6.2.2 renders undefined, linkage disagreement

extern int i1; // refers to pre vious, whose linkage is external
extern int i2; // refers to pre vious, whose linkage is internal
extern int i3; // refers to pre vious, whose linkage is external
extern int i4; // refers to pre vious, whose linkage is external
extern int i5; // refers to pre vious, whose linkage is internal

Письмо 2. Привожу фрагмент, т.к. на это письмо ссылаются далее

Мне удалось найти ссылку на стандарт, которая подтверждает допустимость
расширения языка и корректной работы программы, имеющей для объекта scope
definition и external linkage более, чем в одном файле.

В стандарте Си-89 это расширение языка описано в приложении G.5.11, а в
стандарте Си-99 — в приложении J.5.11. Они абсолютно идентичны, поэтому привожу
текст из Си-99:

В случае, когда в двух разных файлах заданы одинаковые определения переменной
(в рассматриваемом примере это int x), которые подразумевают
инициализацию этой переменной одним и тем же значением (по языку Си — нулем),
это означает, что определения согласованы, а, значит, такая программа должна
успешно «склеивать» эти два определения в одно при сборке программы.

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

. и вопрос, прежде всего, состоит в следующем: является ли имя x в рамках
каждого модуля трансляции (т.е. файла) идентификатором суть одного и того же
единственного объекта (или же двух различных).

Так вот, что имеем (далее все ссылки и формулировки даю по ревизии 1999
года — частично эти цитаты выше уже приводились — здесь, однако, для полноты
дальнейшего повествования мне придется их повторить еще раз):

6.2.1. Scope of identifiers

Вводятся определения:
— видимости;
— области видимости;
— типа области видимости;
— понятия одинаковых областей видимости двух идентификаторов.

For each different entity that an identifier designates, the identifier is
visible (i.e., can be used) only within a region of program text called its
scope. Different entities designated by the same identifier either have
different scopes, or are in different name spaces. There are four kinds of
scopes: function, file, block, and function prototype.

Every other identifier has scope determined by the placement of its declaration
(in a declarator or type specifier). If the declarator or type specifier that
declares the identifier appears outside of any block or list of parameters, the
identifier has file scope, which terminates at the end of the translation unit.
[. ]

Two identifiers have the same scope if and only if their scopes terminate at
the same point.

6.2.2 Linkages of identifiers

Вводятся определения:
— связывания;
— типа связывания;

****************************
А также, что очень важно, именно здесь четко и однозначно фиксируется, что для
набора модулей трансляции (т.е. файлов, включая библиотеки, которые в целом
составляют программу), каждая декларация отдельного идентификатора
с внешним связыванием обозначает ОДИН И ТОТ ЖЕ ОБЪЕКТ (. ).

Далее, в абзацах 4-5 фиксируется вид связывания для случаев, подобных
рассматриваемой языковой ситуации, откуда вытекает, что оба идентификатора x в
этом примере имеют внешнее связывание.
****************************

An identifier declared in different scopes or in the same scope more than once
can be made to refer to the same object or function by a process called
linkage. There are three kinds of linkage: external, internal, and none.

In the set of translation units and libraries that constitutes an entire
program, each declaration of a particular identifier with external linkage
denotes the same object or function.

For an identifier declared with the storage-class specifier extern in a scope
in which a prior declaration of that identifier is visible, if the prior
declaration specifies internal or external linkage, the linkage of the
identifier at the later declaration is the same as the linkage specified at the
prior declaration. If no prior declaration is visible, or if the prior
declaration specifies no linkage, then the identifier has external linkage.

[. ] If the declaration of an identifier for an object has file scope and no
storage-class specifier, its linkage is external.

В принципе, на этом можно и остановиться. Из приведенных цитат вытекает, что:

— оба идентификатора x имеют файловую область видимости, в силу 6.2.1/4;

— в силу 6.2.1/5 их области видимости между собой различны (т.е. совпадают
именно типы областей видимости, а не сами области видимости), поэтому, в силу
6.2.2/1 можно говорить о связывании;

— обе декларации идентификатора x имеют внешнее связывание, в силу 6.2.2/5;

— следовательно, в силу 6.2.2/2, обе декларации идентификатора x обозначают
один и тот же объект.

Таким образом, согласно ОСНОВНОМУ тексту стандарта, с точки зрения связывания,
описанную в исходном примере ситуацию всякая конформная реализация языка
ОБЯЗАНА трактовать совершенно однозначно, т.е. даже без extern обе декларации
идентификатора x здесь описывают один и тот же объект, и компилятор ДОЛЖЕН
себя вести только так, и никак иначе.

— Далее к основному вопросу (на форуме) не относится, но полезно

Что же касается проблем, то — да, они могут возникнуть с т.з. инициализации
такого рода объекта, но это уже второй вопрос, который мы далее и рассмотрим. В
любом случае, объекты плодиться и размножаться не имеют права, согласно
вышеизложенному.

Так вот, что касается тонкостей инициализации, то этот как раз описывается в
процитированных выше пункте 6.9.2, а также информативном приложении J в части
пункта J.5.11, которые посвящены частному случаю декларации, а именно,
дефиниции (намеренно не перевожу слова декларация и дефиниция как объявление и
определение, чтобы не было терминологической путаницы).
Дефиниция — по определению, это декларация, которая приводит к выделению
storage (см. 6.7), т.е. пространства для хранения объекта.

Согласно 6.9.2, в рамках рассматриваемого рода идентификаторов, коротко, имеет
место следующее:

— декларация с файловой областью видимости и инициализатором является т.н.
«внешней дефиницией» (неважно, есть extern или нет — стандарт понимается
буквально!);

— декларация с файловой областью видимости без инициализатора и без extern
является т.н. «предварительной дефиницией»; если в данном модуле трансляции
(файле) есть одна или более предварительных дефиниций и нет ни одной внешней
дефиниции, то реализация языка должна себя в такой ситуации вести так, как если
бы в данном модуле трансляции была декларация с инициализатором «ноль» (в то же
время, стандарт не говорит явно, что эта ситуация будет идентична внешней
дефиниции. ).

Ну и, наконец, пункт J.5.11 разъясняет как раз ситуацию, когда для
идентификатора существует более одной внешней дефиниции (вне зависимости от
наличия extern) — к нему выше дал комментарии (Письмо 2)

Однако, я бы несколько развил толкование применимости J.5.11.

Рассмотрим такой пример:

/* file1.c */
int x = 5;

— по терминологии 6.9.2 выходит, что во втором файле, строго говоря, декларация
x имеет инициализатор ‘= 0’, но она не является «внешней дефиницией», а раз
так, то есть основания полагать, что она еще не подпадает под пункт J.5.11,
т.е. она не относится к «Общим расширениям» стандарта, а является полностью
корректной с точки зрения его основной части, и ОБЯЗАТЕЛЬНА к поддержке
компилятором. И тем более таковой же будет являться исходная ситуация:

— тоесть она действительно ДОЛЖНА корректно отрабатываться компилятором.

При таком понимании 6.9.2 и J.5.11, непосредственно под оговорки J.5.11 тогда
будет подпадать, например, такая пограничная ситуация:

/* file1.c */
int x = 5;

/* file2.c */
int x = 5;

— которая, будучи в таком случае уже undefined, в принипе, может быть
однозначно разрешена реализацией.

Ну, и, наконец, предельный случай для J.5.11, это

/* file1.c */
int x = 5;

/* file2.c */
int x = 6;

— здесь, конечно, это чистый undefined.

Конечно, такой взгяд на вещи может показаться надуманным, но, что интересно,
именно он в точности подтверждается поведением gcc, а именно, на gcc
нормально и однозначно работает исходная ситуация (переменная инициализируется
нулем), нормально и однозначно, без каких бы то ни было warning’ов, работает
ситуация A1 (переменная инициализируется значанием 5), а вот примеры A2 и A3
как раз выламываются на линковке!

Что значит «явно не говорит»?

«If a translation unit contains one or more tentative definitions for an identifier, and the translation unit contains no external definition for that identifier, then the behavior is exactly as if the translation unit contains a file scope declaration of that identifier, with the composite type as of the end of the translation unit, with an initializer equal to 0

и стандарт говорит

«If the declaration of an identifier for an object has file scope and an initializer, the declaration is an external definition for the identifier.»

Складывая эти два требования стандарта вместе, получаем, что tentaive definition превращается в external definition к концу translation unit. Вот и все.

Стандарт не выполняет и не обязан явно выполнять все «транзитивные замыкания» содержащихся в стандарте требований и утверждений.

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

С чего бы это вдруг «не является»? Согласно стандарту языка, поведение такой декларации в file2.c полностью эквивалентно («the behavior is exactly as») поведению декларации

то есть она создает external definition. Где это в стандарте сказано, что из «the behavior is exactly as» вдруг надо исключить создание external definition?

Более того, а если у нас вся программа состоит из одного только файла file2.c и функции main, доступающейся к переменой x, то согласно вашей логике у нас в программе вообще нет external definition для x и программа некорректна. Так получается?

«If a translation unit contains one or more tentative definitions for an identifier, and the translation unit contains no external definition for that identifier, then the behavior is exactly as if the translation unit contains a file scope declaration of that identifier, with the composite type as of the end of the translation unit, with an initializer equal to 0.»

и стандарт говорит

«If the declaration of an identifier for an object has file scope and an initializer, the declaration is an external definition for the identifier.»

Я воспринимаю эту ситуацию как что-то похожее на юридический закон. Зависит от того, как трактовать. Я не являюсь большим специалистом по английскому языку и тонкостей формулировок на английском языке, но я не вижу, почему твоя трактовка должна быть более предпочтительнее, чем трактовка из писем, изложенная выше. Конкретно в твоём случае трактовка базируется на том, что ты выводишь заключение за счёт того, что переставляешь местами пункты 6.9.2.1 и 6.9.2.1 (с точки зрения причинно-следственной связи), а в письме делают заключение исходя из того, что пункты должны быть трактованы в прямом порядке

Что имели в виду афторы языка Си (я горою именно об афторах языка, а не об афторах стандарт) — фиг его знает. Стандарт (или юридический закон) — это один из способов, при котором простая и понятная вещь может превратиться в множество сложных пунктов

По поводу случая с инициализацией я пока не хочу говорить, т.к. и без того уже мозг кипит

Эта тема, кстати, хорошо освещена в Rationale for C99: http://www.open-std.org/jtc1/s. eV5.10.pdf (стр. 32-34)

В моем переводе:

Модель определений, которая должна использоваться для объектов с внешним связыванием, была серъезным камнем преткновения при стандартизации С89. Базовая проблема заключалась в том, чтобы решить, какие объявления резервируют хранилище для объекта, а какие являются просто ссылками на существующий объект. Сопутствующей проблемой было то, разрешать ли множественные определения для объекта или требовать, чтобы определение было единственным. В до-С89 реализациях существовало как минимум четыре разных модели, приведенных ниже в порядке увеличения их строгости:

Общая (Common)
Каждое объявление объекта с внешним связыванием, независимо от того, содержит ли оно ключевое слово extern , создает определение в хранилище. Когда все модули объединяются в единое целое, все определения с одинаковыми именами размещаюся по одному и тому же адресу в памяти. (Имя этой модели позаимствовано от «общей памяти» (common storage) в Фортране.) Эту модель намеревался использовать оригинальный разработчик языка С Денис Ритчи.

Расслабленная (Relaxed Ref/Def)
Наличие ключевого слова extern в объявлении, независимо от того, используется ли оно внутри функции или снаружи, обозначает чистую сcылку (ref), которая не резервирует места в хранилище. Где-то среди всех единиц трансляции должно присутствовать как минимум одно определение объекта (def). Внешнее определение — это объявление объекта, располагающееся в файловой области видимости и не содержащее никакого спецификатора класса памяти. Ссылка, которой не соответствует определения, является ошибкой. Некоторые реализации также не генерируют ссылок для объектов, объявленных с ключевым словом extern , но не использующихся в коде. Компилятор языка С в операционной системе UNIX реализовал именно эту модель, которая признается популярным расширением языка С (см. J.5.11). Программы на С для UNIX, которые используют эту модель, соответствуют стандарту в их среде, но не являются полностью портируемыми (не строго соответствуют стандарту языка).

Строгая (Strict Ref/Def)
Совпадает с Расслабленой, с той только разницей, что разрешается лишь одно-единственное определение. Опять же, некоторые реализации могут принять решение не создавать ссылок для неиспользуемых объектов. Это — модель, предписываемая K&R.

Инициализационная (Initialization)
Эта модель требует явно указанного инициализатора для создания определения в хранилище. Все остальные объявления — это просто ссылки.

Стандартная модель является комбинацией свойств Строгой модели и Инициализационной модели. В соответствии со Строгой моделью, только одна единица трансляции содержит определение каждого объекта, т.к. многие среды не в состоянии эффективно поддерживать «распределенные определения», характерные для Общего или Расслабленного подхода. Однако либо инициализация, либо определение без спецификатора класса памяти служат в качестве внешнего определения. Этот комбинированный подход был выбран для того, чтобы учесть особенности максимально широкого набора сред и существующих реализаций.

Далее наличествует иллюстрационная табличка

Источник

Читайте также:  Dtv кабель как настроить
Оцените статью