Различные результаты компиляции, не использующие extern в C vs in С++
Когда я объявляю глобальную переменную в двух разных исходных файлах и определяю ее только в одном из исходных файлов, я получаю различные компиляции результатов для C++, чем для C. См. Следующий пример:
main.c
#include <stdio.h>
#include "func.h" // only contains declaration of void print();
int def_var = 10;
int main() {
printf("%d\n", def_var);
return 0;
}
func.c
#include <stdio.h>
#include "func.h"
/* extern */int def_var; // extern needed for C++ but not for C?
void print() {
printf("%d\n", def_var);
}
Я компилирую следующие команды:
gcc/g++ -c main.c -o main.o
gcc/g++ -c func.c -o func.o
gcc/g++ main.o func.o -o main
g++/clan g++ жалуются на multiple definition of def_var
(это поведение, которое я ожидал, когда не использовал extern). gcc/clang компилируется просто отлично. (с использованием gcc 7.3.1 и clang 5.0)
Согласно этой ссылке:
Предварительное определение - это объявление, которое может или не может выступать в качестве определения. Если фактическое внешнее определение найдено раньше или позже в той же самой системе перевода, то предварительное определение просто действует как декларация.
Поэтому моя переменная def_var
должна быть определена в конце каждой единицы перевода, а затем приводить к нескольким определениям (как это сделано для C++). Почему это не так при компиляции с gcc/clang?
Ответы
Ответ 1
Это, строго говоря, неверно. Говорит так же в
6.9 Внешние определения - p5
Внешнее определение - это внешнее объявление, которое также является определением функции (отличной от встроенного определения) или объекта. Если идентификатор, объявленный с внешней связью, используется в выражении (кроме как в части операнда оператора sizeof или _Alignof, результат которого является целочисленной константой), где-то во всей программе должно быть ровно одно внешнее определение для идентификатора; в противном случае должно быть не более одного.
У вас есть два определения для идентификатора с внешней связью. Вы нарушаете это требование, поведение не определено. Связь с программой и работа не противоречат этому. Это не требуется для диагностики.
И стоит отметить, что C++ не отличается в этом отношении.
[basic.def.odr]/4
Каждая программа должна содержать ровно одно определение каждой не-встроенной функции или переменной, которая не используется в этой программе вне отбрасываемого оператора; не требуется диагностика. Определение может отображаться явно в программе, оно может быть найдено в стандартной или определяемой пользователем библиотеке или (при необходимости) неявно определено (см. [Class.ctor], [class.dtor] и [class.copy ]). Встроенная функция или переменная должны быть определены в каждой единицы перевода, в которой она используется вне вне отбрасываемого оператора.
Опять же, требование "должно", и оно прямо говорит, что никакой диагностики не требуется. Как вы, возможно, заметили, существует довольно много механизмов, к которым этот пункт может применяться. Таким образом, передние конечные точки для GCC и Clang, вероятно, должны работать усерднее, и, как таковые, могут диагностировать его, несмотря на то, что этого не требуется.
Программа плохо организована в любом случае.
Как отметил М. в комментарии, в стандарте C есть информативный раздел, в котором упоминается само расширение в ответе zwol.
J.5.11 Множество внешних определений
Для идентификатора объекта может быть несколько внешних определений с явным использованием ключевого слова extern; если определения не согласуются или более одного инициализируется, поведение не определено (6.9.2).
Ответ 2
Я считаю, что вы наблюдаете расширение C, известное как " общие символы ", реализованное большинством, но не всеми компиляторами Unix-lineage C, изначально (IIUC) для совместимости с FORTRAN. Расширение обобщает правило "предварительных определений", описанное в рассказе StoryTeller, на несколько единиц перевода. Все определения внешних объектов с тем же именем и без инициализатора,
int foo; // at file scope
сворачиваются в один, даже если они отображаются в более чем одном TU, и если существует внешнее определение с инициализатором для этого имени,
int foo = 1; // different TU, also file scope
то все внешние определения без инициализаторов рассматриваются как внешние объявления. C++ компиляторы не реализуют это расширение, потому что (упрощение) никто не хотел выяснить, что он должен делать при наличии шаблонов. Для GCC и Clang вы можете отключить расширение с помощью -fno-common
, но другие компиляторы Unix C могут не иметь возможности отключить его.