В языке Си если глобальная переменная об'явлена в одном файле, то в другом она видна не будет. Надо во всех других файлах, где она нужна, написать дублирующее об'явление, но с пометкой extern. Например, в первом файле:
int i;
А во втором:
extern int i;
Я сделал одну такую переменную, допустим var. После этого проект перестал собираться. Компоновщик (MSVS 2015) сообщал, что в одном из модулей уже есть такая переменная. Она действительно была, но внутри класса, поэтому не должна была мешать. Я даже включил отдельную работу препроцессора -- но в файле после препроцессора никаких дополнительных об'явлений var не обнаружил.
Я переименовал переменную. Теперь это была не var, а var2. После этого сообщение об ошибке изменилось. Компоновщик стал опять говорить, что теперь переменная var2 в модуле уже есть. Но на этот раз её не было даже в составе класса. Это было совершенно непонятно, и я загуглил. Гугл предлагал проверить, что типы обычного и erxtern об'явления совпадают. Они заведомо совпадали, т.к. extern об'явления я делаю копипастом, но я решил проверить, как я на самом деле написал об'явления и сравнить их побуквенно. И вот что я обнаружил:
float var2=1.0f;
extern float var2=1.0f;
Копипаст меня и подвёл. Да, дело было в инициализации. extern либо не умеет инициализировать переменные, либо плохо относится к инициализации в нескольких модулях сразу. Я убрал инициализацию из extern-об'явления -- и всё прекрасно собралось.
Таким образом, сообщение об ошибке компоновщика не отражало реальности.
int i;
А во втором:
extern int i;
Я сделал одну такую переменную, допустим var. После этого проект перестал собираться. Компоновщик (MSVS 2015) сообщал, что в одном из модулей уже есть такая переменная. Она действительно была, но внутри класса, поэтому не должна была мешать. Я даже включил отдельную работу препроцессора -- но в файле после препроцессора никаких дополнительных об'явлений var не обнаружил.
Я переименовал переменную. Теперь это была не var, а var2. После этого сообщение об ошибке изменилось. Компоновщик стал опять говорить, что теперь переменная var2 в модуле уже есть. Но на этот раз её не было даже в составе класса. Это было совершенно непонятно, и я загуглил. Гугл предлагал проверить, что типы обычного и erxtern об'явления совпадают. Они заведомо совпадали, т.к. extern об'явления я делаю копипастом, но я решил проверить, как я на самом деле написал об'явления и сравнить их побуквенно. И вот что я обнаружил:
float var2=1.0f;
extern float var2=1.0f;
Копипаст меня и подвёл. Да, дело было в инициализации. extern либо не умеет инициализировать переменные, либо плохо относится к инициализации в нескольких модулях сразу. Я убрал инициализацию из extern-об'явления -- и всё прекрасно собралось.
Таким образом, сообщение об ошибке компоновщика не отражало реальности.
21.06.2020 в 17:45
А всё дело в том, что `extern float var2 = 1.0f` — это определение (definition), а не объявление (declaration). То есть оно резервирует место под переменную, и это видит линковщик. Пруф: смотри на _DATA SEGMENT в godbolt.org/z/Cv-CDG Так что сообщение всё правильно отражало, просто в типичной сишной манере: «я тебе намекну, а ты уж сам разберись».
А вот GCC в этом месте выдаёт предупреждение (пруф):
> warning: 'var2' initialized and declared 'extern'
И это, замечу, GCC 4.1 четырнадцатилетней давности! И безо всяких дополнительных флагов. Если есть возможность, компилируй время от времени с помощью MingW и вычищай все предупреждения.
-- Minoru
21.06.2020 в 18:02
Воу, спасибо за комментарий.
а ГДЕ оно резервирует место?.......... глобальная переменная существует в единственном экземлпяре и место под неё резервируется в том модуле, где настоящее объявление.
21.06.2020 в 18:52
В пруфе это _DATA SEGMENT. Я в структурах бинарников (EXE, ELF etc.) не разбираюсь, поэтому не могу более подробно объяснить.
глобальная переменная существует в единственном экземлпяре
«Глобальная переменная» это термин в голове программиста, компилятор ничего такого не знает. Зато знает про «переменные с внешней линковкой» и про «extern-объявления». Это как кубики. Если программист их правильно совместит — получится некая конструкция, которую программист зовёт «глобальная переменная». А если совместит неправильно, получит либо ошибку на каком-то из этапов компиляции, либо просто undefined behavior.
Кстати да, я посмотрел в черновики C89 и C99; оба утверждают, что несколько определений одной и той же переменной с внешней линковкой — это UB. Причём переменные на уровне файла имеют внешнюю линковку по умолчанию, так что если ты просто в двух файлах .c напишешь `int x`, ты тоже получишь эту ошибку линковщика. И с определениями функций та же фигня: пишешь в двух файлах `void f() {}` и получаешь ошибку. Чтобы этого избежать, нужно везде, кроме глобальных переменных и экспортируемых функций, писать спецификатор static.
место под неё резервируется в том модуле, где настоящее объявление.
Только при условии, что программист правильно совместил кубики. Тогда действительно есть одно «настоящее» объявление, может быть, даже без инициализации (`float var2`). Все остальные, «ненастоящие», объявления помечены спецификатором `extern` и ничего не резервируют.
А ты, получается, совместил кубики неправильно: `extern float var2 = 1.0f` это определение, спецификатор `extern` здесь ни на что не влияет (переменные, объявленные на уровне файла без спецификаторов, и так имеют внешнюю линковку). То есть вместо кубика «определение» и кубиков «extern-объявления» ты взял два кубика «определение». Поэтому у тебя получилась не глобальная переменная, а ошибка линковщика.
-- Minoru