Многократные ошибки определения из-за включения одного и того же заголовка в несколько cpps - PullRequest
14 голосов
/ 22 октября 2008

Итак, независимо от того, что я делаю, я не могу избежать того, что Dev C ++ из-за того, что я включил один и тот же заголовочный файл в несколько файлов исходного кода в одном и том же проекте, выложил многочисленные ошибки множественного определения. Я бы предпочел не делать сваливания всего моего исходного кода в один файл и включать заголовок только один раз, так как это сделает мой файл очень длинным и сложным в управлении.

По сути, это то, что происходит:

#ifndef _myheader_h
#define _myheader_h

typedef struct MYSTRUCT{
int blah;
int blah2; } MYSTRUCT;

MYSTRUCT Job_Grunt;
MYSTRUCT *Grunt = &Job_Grunt;
MYSTRUCT Job_Uruk;
MYSTRUCT *Uruk = &Job_Grunt;

int Other_data[100];

void load_jobs();

#endif

Пример Cpp-файла (все они выглядят примерно так):

#include "myheader.h"

void load_jobs(){

Grunt->blah = 1;
Grunt->blah2 = 14;

Uruk->blah = 2;
Uruk->blah2 = 15;

return; }

Имейте в виду, что у меня есть около 5 файлов cpp, которые включают этот заголовок, каждый из которых имеет свой тип структуры, найденный в заголовочном файле. В этом примере была только одна структура, содержащая пару элементов, когда в реальном заголовочном файле есть около 4-6 различных структур с гораздо большим числом членов. Все файлы, в которые я включил его, следуют той же формуле, как вы видите в этом примере здесь.

Теперь я понимаю, что защита заголовка не позволяет каждому отдельному файлу cpp включать файл заголовка более одного раза. Казалось бы, что происходит, когда компилятор считывает include в начале каждого cpp, он снова определяет файл заголовка, что заставляет его выплевывать строки и строки:

Multiple Definition of Uruk, first defined here  
Multiple Definition of Job_Uruk, first defined here  
Multiple Definition of Grunt, first defined here  
Multiple Definition of Job_Grunt, first defined here  
Multiple Definition of Other_data, first defined here

Я посмотрю этот набор почти для каждого файла cpp в проекте, который включает в себя заголовок. Я попытался переместить определения переменных struct и struct в файлы cpp, но затем другие файлы cpp не могут их видеть или работать с ними, что очень важно, так как мне нужны все файлы в проекте, чтобы иметь возможность работать с этими структурами.

Но самая запутанная часть этой проблемы требует немного большего объяснения:

То, как я настраиваю эти несколько файлов в этом проекте, идентично книге, с которой я работаю, «Все в одной игре» Джона С. Харбора. Я столкнулся с точно такими же проблемами, когда создавал файлы для примеров проектов в книге, в которых требовался один заголовок, включаемый несколькими cpps в одном проекте.

Я мог бы напечатать их слово в слово из книги, и я имею в виду слово в слово ...
и я бы получил серию ошибок MD для каждого cpp в проекте.

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

Если бы я создал свой собственный файл проекта и просто добавил исходный файл и файлы заголовков для примера проекта с компакт-диска, он также скомпилировался бы и запустился, хотя я не могу найти разницы между ними и моим.

Итак, я попытался создать свой собственный файл проекта, затем создать пустые исходные и заголовочные файлы и добавить их в него, а затем заполнить их, скопировав и вставив их содержимое из файлов на компакт-диск, которому они должны были соответствовать (те же, что работали). И конечно же, я получил бы то же самое ... строки и строки сообщений об ошибках MD.

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

Ответы [ 9 ]

22 голосов
/ 22 октября 2008

Поскольку вы объявляете эти переменные в заголовочном файле и включаете заголовочный файл в каждый файл C ++, каждый файл C ++ имеет свою собственную копию.

Обычный способ обойти это - , а не объявлять любые переменные в заголовочных файлах. Вместо этого объявите их в одном файле C ++ и объявите их как extern во всех других файлах, в которых они могут вам понадобиться.

Другой способ, которым я раньше занимался, который некоторые могут посчитать неприятным ... объявить их в заголовочном файле, например так:

#ifdef MAINFILE
    #define EXTERN
#else
    #define EXTERN extern
#endif

EXTERN MYSTRUCT Job_Grunt;
EXTERN MYSTRUCT *Grunt = &Job_Grunt;
EXTERN MYSTRUCT Job_Uruk;
EXTERN MYSTRUCT *Uruk = &Job_Uruk;

Затем в одном ваших файлов C ++ добавьте ...

#define MAINFILE

... до ваших #include строк. Это обо всем позаботится, и (по моему личному мнению) намного приятнее, чем необходимость переопределять все переменные в каждом файле.

Конечно, решение real не в том, чтобы вообще использовать глобальные переменные, но когда вы только начинаете, этого трудно достичь.

12 голосов
/ 22 октября 2008

Когда вы определяете переменную, компилятор выделяет память для этой переменной. Определив переменную в заголовочном файле и включив этот файл во все исходные файлы, вы определяете одну и ту же переменную в нескольких файлах.

Помещение ключевого слова extern перед определением переменной сообщит компилятору, что эта переменная уже где-то определена, и что вы только объявляете (то есть присваиваете имя) переменную, чтобы другие файлы могли использовать это.

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

extern MYSTRUCT Job_Grunt;
extern MYSTRUCT *Grunt;
extern MYSTRUCT Job_Uruk;
extern MYSTRUCT *Uruk;

extern int Other_data[100];

А затем в одном (и только одном) из ваших исходных файлов обычно определяют переменные:

MYSTRUCT Job_Grunt;
MYSTRUCT *Grunt = &Job_Grunt;
MYSTRUCT Job_Uruk;
MYSTRUCT *Uruk = &Job_Grunt;

int Other_data[100];
6 голосов
/ 22 октября 2008

Хотя большинство других ответов верны относительно того, почему вы видите несколько определений, терминология неточна. Понимание декларации и определения - ключ к вашей проблеме.

Объявление объявляет о существовании предмета, но не вызывает его создания. Следовательно, внешние выражения являются декларациями, а не определениями.

Определение создает экземпляр определенного элемента. Следовательно, если у вас есть определение в заголовке, оно создается в каждом файле .cpp, что приводит к нескольким определениям. Определения также являются объявлениями - т.е. отдельное объявление не требуется, если, например, область действия элемента ограничена одним файлом .cpp.

Примечание: использование слова "экземпляр" здесь действительно применимо только к элементам данных.

4 голосов
/ 22 октября 2008

Вам необходимо определить ваши переменные как extern в заголовочном файле, а затем определить их также в файле cpp. i.e.:

extern MYSTRUCT Job_Grunt;

в вашем заголовочном файле, а затем в файле cpp в вашем проекте объявите их как обычно.

Файл заголовка предназначен только для определений, когда вы создаете экземпляр переменной в файле заголовка, он будет пытаться создавать его каждый раз, когда заголовок включается в ваш проект. Использование директивы extern указывает компилятору, что это всего лишь определение и что создание экземпляра выполняется где-то еще.

2 голосов
/ 07 июня 2011

Я также получил эту ошибку для функции, определенной в файле .h. Заголовочный файл не предназначен для создания объявлений класса, но содержит определения некоторых функций, которые необходимы в разных местах проекта. (Я могу запутать использование «определения» и «объявления», но я надеюсь, что смогу дать основную идею.) Когда я помещаю ключевое слово «inline» непосредственно перед определением функции, которое выдает ошибку «множественные определения», ошибки избегают

0 голосов
/ 23 июня 2009

Вот что у меня сработало: связать источники в отдельные библиотеки. (Моя проблема была не в создании программы, а в одной / многих библиотеках.) Затем я (успешно) связал одну программу с двумя библиотеками, которые я создал.

У меня было два набора функций (одна зависит от другой) в одном и том же исходном файле, и они были объявлены в одном и том же заголовочном файле. Затем я попытался разделить два набора функций на два заголовка + исходные файлы.

Я пробовал оба раза #pragma и включил охрану с #ifndef ... #define ... #endif. Я также определил переменные и функции как внешние в заголовочных файлах.

Как указал Стив Фэллоуз, проблема не в компиляции, а в связи. В моей конкретной проблеме мне не помешало иметь два набора функций, каждый в своем исходном файле, скомпилировать и затем связать в две отдельные библиотеки .

g++ -o grandfather.o -c grandfather.cpp
g++ -o father.o -c father.cpp
g++ -fPIC -shared -o libgf.so grandfather.o
g++ -fPIC -shared -o libfather.so father.o

Это заставляет меня связывать свои программы как с libgf.so, так и с libfather.so. В моем конкретном случае это не имеет значения; но в противном случае я не смог бы заставить их работать вместе.

0 голосов
/ 19 апреля 2009

У меня тоже была эта проблема некоторое время назад. Позвольте мне попытаться объяснить, что решило это. У меня был файл global.h, который имел все объявления и должен был быть включен в каждый файл cpp. Вместо того, чтобы включать его в каждый .cpp, я включил его в .h. Во все мои файлы ".h" я добавил строки #ifndef и #define и закончил #endif. Это решило проблему MD. Надеюсь, это работает и для вас.

0 голосов
/ 22 октября 2008

GCC 3.4 и выше поддерживает #pragma once. Просто поставьте #pragma once в верхней части своего кода, вместо того чтобы использовать include guard. Это может быть или не быть более успешным, но оно того стоит. И нет, это (не всегда) точно эквивалентно включенному охраннику.

0 голосов
/ 22 октября 2008

Чтобы расширить то, что сказал Джеральд, заголовок определяет экземпляр структуры (это не то, что вам нужно). Это приводит к тому, что каждый модуль компиляции (файл cpp), который включает в себя заголовок, получает свою собственную версию экземпляра структуры, что вызывает проблемы во время компоновки.

Как сказал Джеральд, вам нужно определить ссылку на структуру (используя 'extern') в заголовке и иметь один файл cpp в вашем проекте, который создает экземпляр.

...