Вот у меня есть небольшая программа:
// header.h
#pragma once
int a;
int reta();
// extra.c
#include "header.h"
int reta() {
return a;
}
// main.c
#include "header.h"
#include <stdio.h>
int main(void) {
printf("%d, %d", a, reta());
}
Результат при компиляции с помощью Visual Studio (стандарт C: c17, Debug x64):
0, 0
Результат при компиляции с помощью GCC (gcc -L. -o test.exe *.c -std=c17 -mwindows -Wl,-subsystem,console):
C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:...\Temp\ccGvFOtk.o:main.c:(.bss+0x0): множественное определение `a'; C:...\Temp\ccANU1lv.o:extra.c:(.bss+0x0): впервые определено здесь Collect2.exe: ошибка: ld вернул 1 статус выхода
Мой вопрос: почему существует разница и что мне делать, чтобы исправить ошибку в случае GCC?
«что мне сделать, чтобы исправить ошибку» — никогда не определяйте переменную в файле H. Туда должны идти только объявления (если только у вас нет особых случаев и вы точно не знаете, что делаете). Переменная должна быть определена в одном источнике. Если вы хотите получить к нему доступ из других источников, укажите его как extern
.
@stark, ок, но почему msvc компилирует без ошибок?
См. stackoverflow.com/q/3663599/1216776
См. обсуждение в Как использовать extern для совместного использования переменных между исходными файлами? , особенно раздел «Не очень хороший способ определения глобальных переменных». GCC 10.1 изменил правила, чтобы они более точно соответствовали стандарту C, решив не использовать вариант, разрешенный Приложением J, в качестве «общего расширения» по умолчанию. Вы видите последствия этого решения.
В заголовочном файле у вас есть предварительное определение a
. Это означает, что оба ваших файла .c, содержащие этот заголовок, будут иметь определение a
. Что делает определение предварительным, так это тот факт, что оно не инициализируется явно.
Некоторые компиляторы, такие как MSVC в данном случае, объединяют несколько предварительных определений в одно определение. Однако версия gcc, которую вы используете, не поддерживает, поэтому вы получаете ошибку множественного определения. Если бы вы инициализировали a
в заголовочном файле, это было бы полное определение, и оба компилятора, скорее всего, выдали бы ошибку.
Правильный способ справиться с этим — объявить a
в заголовочном файле и определить его ровно в одном исходном файле.
// header.h
#pragma once
extern int a; // declaration of a
int reta();
// extra.c
#include "header.h"
int a; // definition of a
int reta() {
return a;
}
// main.c
#include "header.h"
#include <stdio.h>
int main(void) {
printf("%d, %d", a, reta());
}
Разве поведение MSVC в данном случае не является стандартным (или находится в области UB)? Насколько я понимаю, предварительное определение становится обычным определением, если в одной единице перевода больше нет определений?
Итак, переменные могут существовать тремя способами: объявлением, определением и инициализацией?
@ItzYurix Переменная может быть определена или объявлена. Определение может дополнительно включать инициализатор.
@EugeneSh.: Поведение GCC и MSVC соответствует стандарту C, который не определяет поведение при наличии нескольких внешних определений объекта, будь то регулярные или вытекающие из предварительных определений.
Вы компилируете два файла .c, main.c и extra.c, а затем связываете их. Оба определяют
a
Измените header.h наextern int a;
и выполните определение в одном из файлов C.