C / C ++ - Условное включение заголовочного файла не работает - PullRequest
2 голосов
/ 10 августа 2011

У меня есть три файла в моем проекте.

a.c
b.c
test.h

test.h объявляет

namespace test_namespace {
    int i;
    void f1();
};

test.h также окружен

#ifndef __x
#define __x
...
#endif

Теперь a.c включает test.h, а b.c также включает test.h. a.c имеет функцию main (), а b.c имеет реализацию test_namespace :: f1 ()

Однако при компиляции я получаю ошибку компоновки -

"test_namespace::i is already defined in <b.c's object file mapping in /tmp>"

Если я позаботился о том, чтобы включить директивы препроцессора условной компиляции в test.h, почему он включен в оба файла a.c и b.c?

Также следует отметить, что если я скомпилирую b.c отдельно в качестве разделяемой библиотеки, а затем использую ее в качестве разделяемой библиотеки при связывании объектного файла a.c, я не получу эту ошибку.

Может кто-нибудь объяснить мне вышеуказанную ошибку, особенно перед лицом директив условной компиляции?

Ответы [ 6 ]

4 голосов
/ 10 августа 2011

Вы не можете объявлять переменные внутри заголовочного файла. Символ test_namespace :: i становится экспортированным как a.c, так и b.c. Компоновщик находит оба и не знает, какой из них использовать.

То, что вы хотите сделать в test.h:

namespace test_namespace {
    extern int i;
    void f1();
}

, а затем объявите test_namespace :: i в eather a.c или b.c:

namespace test_namespace {
    int i;
}
3 голосов
/ 10 августа 2011

Условное включение используется для предотвращения включения заголовков дважды для одного и того же исходного файла не для всего проекта. Предположим, у вас есть заголовки a.h и b.h и b.h #include s a.h. Тогда, если c.c нужны вещи из обоих заголовков, он #include из них обоих. Поскольку препроцессор C использует буквенную подстановку текста, когда он включает b.h, теперь в вашем файле появятся две директивы #include "a.h", что приведет к разрушению нескольких объявлений. (Изменить: уточнить, почему у вас возникают проблемы в этом случае.)

2 голосов
/ 10 августа 2011

Включены средства защиты для защиты нескольких включений header в сборке модуля компиляции.Они не нужны в случаях, когда у вас есть два отдельных файла кода и один заголовок, как в вашем примере.

(поэтому подумайте больше, когда test.c использует a.h и b.h, в тех случаях, когда b.h необходимо #include a.h.)

Но это примечание очто делает конвенция include guard и как она ничего не покупает в этом случае.Конкретная техническая проблема, с которой вы столкнулись (как уже отмечали другие), заключается в том, что вы в основном определяете одну и ту же переменную в двух разных объектных файлах, и когда компоновщик собирается собрать все вместе, он не знает, хотите ли вы переменную из a.o или b.o.

(Примечание. Хотя компиляторы, как правило, можно настроить на переопределение и создание кода C ++ с использованием таких функций, как namespace, даже если расширение .c - вам, вероятно, следуетиспользовать что-то еще, например .cpp: расширение файла кода C ++? .cc vs .cpp )

1 голос
/ 10 августа 2011

Защита заголовка (#ifndef .. #define .. #endif) работает как надо. И a.c и b.c включают test.h , поэтому они оба получают копию этого заголовка. (Когда вы компилируете программу, #include буквально копирует и вставляет содержимое заголовка в исходный файл.)

Поскольку у них обоих есть копия заголовка, они оба определяют переменную test_namespace::i. Когда компоновщик пытается связать код, сгенерированный из a.c , с кодом, сгенерированным из b.c , он находит, что они оба определяют эту переменную. Он не знает, что делать, поэтому не завершает работу и выдает ошибку.

1 голос
/ 10 августа 2011

Происходит следующее: когда вы говорите

int i;

, это действительно две вещи:

1) Объявляет символ i

2) Зарезервирует некоторое пространство и символ для i в объектном файле

Хитрость заключается в том, что (1) должно быть сделано только один раз для файла (на самом деле вы можете повторить это в этом случае) - чтоВот почему у вас есть условное включение, которое вы сделали правильно - но (2) должно выполняться только один раз для программы

Решение состоит в том, чтобы заголовок выполнял

// Only declare -- don't create a symbol
extern int i;

А в файле * .c сделать

int i;
1 голос
/ 10 августа 2011

Вы определяете test_namespace::i в шапке.Что вам, вероятно, нужно, это extern int i; в заголовке и определение в одном из исходных файлов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...