Нет предупреждения с неинициализированной строкой C - PullRequest
0 голосов
/ 02 июля 2018

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

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

const char* const VERSION;

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

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

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

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

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

Ответы [ 3 ]

0 голосов
/ 02 июля 2018

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

extern const char* const VERSION;

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

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

РЕДАКТИРОВАТЬ: - Также необходимо убедиться, что только один исходный файл определил переменную VERSION, в то время как другие исходные файлы объявили ее extern, и вы можете сделать это, определив ее в источнике файл VERSION.c и поместите объявление extern в заголовочный файл VERSION.h.

0 голосов
/ 02 июля 2018

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

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

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

Это

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

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

* * Пример тысяча двадцать-один: * * 1 022

v.c:

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";
0 голосов
/ 02 июля 2018

У вас есть определенная (не только объявленная) переменная в заголовке.

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

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

...

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

...

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

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

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

...