Нет предупреждения с неинициализированной строкой C

В настоящее время я задаюсь вопросом, почему я не получаю ошибку от GCC во время компиляции/связывания небольшой программы на C.

Я объявил в version.h следующую строку:

const char* const VERSION;

В version.c я установил инициализацию переменной:

const char* const VERSION = "0.8 rev 213";

Нет проблем с этим. Я могу использовать строку в остальной части программы.

Если файл c отсутствует, во время компиляции/компоновки ошибок не возникает, но программа не работает с SIGSEGV (конечно), когда пытается получить доступ к переменной.

Является ли мой способ настройки переменной VERSION правильной или есть лучший способ? Или есть шанс получить ошибку во время компиляции/ссылки?

Ответ 1

Вы определили (не просто объявили) переменную в заголовке.

Если вы когда-либо включали этот заголовок из более чем одного исходного файла, поведение не определено. Здесь соответствующая цитата из стандарта:

J.2 Неопределенное поведение

...

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

...

Здесь вы полагаетесь на специфическое для GCC (на самом деле общее для многих компиляторов, но все же нестандартное) поведение, которое объединяет дубликаты предварительных определений данных. См. -fcommon по -fcommon и -fno-common флагам компиляции GCC. Не все компиляторы ведут себя так. Исторически это обычное поведение для линкеров, потому что так, как работал Fortran до того, как было C.

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

Короче говоря, никогда не делай этого. Чтобы объявить (но не определить) глобальную переменную в заголовке, используйте extern. Если вы это сделаете и попытаетесь опустить определение в другом месте, вы, скорее всего, получите ошибку компоновщика (хотя стандарт не требует диагностики для этого нарушения, вся эта известная реализация дает один).

Ответ 2

Ваш пример работает из-за встроенной в Fortran (неправильной) функции C (но не C++), называемой предварительными определениями (6.9.2p2), которая обычно нестандартно распространяется на несколько файлов.

Ориентировочные определения - это объявления переменных без extern и без инициализатора. В обычных реализациях (предназначенных для каламбур) предварительные определения создают особый вид символа, который называется common символом. Во время связывания в присутствии регулярного символа с тем же именем другие общие символы становятся ссылками на обычный символ, что означает, что вся пустая VERSION в ваших единицах перевода, созданная там из-за включений, станет ссылкой на обычный символ const char* const VERSION = "0.8 rev 213"; , Если нет такого регулярного символа, общие символы будут объединены в одну нулевую инициализированную переменную.

Я не знаю, как получить предупреждение от этого с помощью компилятора C.

это

const char* const VERSION;
const char* const VERSION = "0.8 rev 213";

похоже, работает с gcc независимо от того, что я пробовал (g++ не примет его - C++ не имеет функции условного определения и ему не нравятся константные переменные, которые явно не инициализированы). Но вы можете скомпилировать с -fno-common (который является довольно "распространенным" (и очень рекомендуемым) нестандартным вариантом (gcc, clang и tcc все это имеет)), а затем вы получите ошибку компоновщика, если неинициализированные и инициализированные декларации без внешнего воздействия находятся в разных единицах перевода.

Пример:

ВК:

const char * VERSION;

main.c

const char* VERSION;
int main(){}

компиляция и связывание:

gcc main.c v.c #no error because of tentative definitions
g++ main.c v.c #linker error because C++ doesn't have tentative definitions
gcc main.c v.c -fno-common #linker error because tentative defs. are disabled

(Я удалил второй const в примере для примера C++ - C++ дополнительно делает константные глобальные константы или что-то подобное, что только усложняет демонстрацию функции предварительного определения.)

Если дефинитивные определения отключены или с C++, все объявления переменных в заголовках должны иметь в них extern

version.h:

extern const char* const VERSION;

и у вас должно быть одно определение для каждого глобального, и это определение должно иметь инициализатор или быть без extern (некоторые компиляторы будут предупреждать, если вы применяете extern к инициализированной переменной).

version.c:

#include "version.h" //optional; for type checking
const char* const VERSION = "0.8 rev 213";

Ответ 3

В version.h вы должны объявить VERSION как extern как

extern const char* const VERSION;

И в version.c вы должны определить внешнюю переменную как

const char* const VERSION = "0.8 rev 213";

EDIT: - Также вам нужно убедиться, что только один исходный файл определил переменную VERSION то время как другие исходные файлы объявили ее extern и вы можете сделать это, определив ее в исходном файле VERSION.c и поместите объявление extern в файл заголовка VERSION.h.