重复多重定义错误,在多个cpps中包含相同的标头

时间:2008-10-21 22:27:46

标签: c++ dev-c++ multiple-definition-error multiple-inclusions

所以,无论我做什么,我都似乎无法避免让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文件多次包含头文件。似乎正在发生的事情是,当编译器在每个cpp的开头读取include时,它会再次定义头文件,这会导致它吐出以下行和行:

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文件无法看到它们或使用它们,这非常重要,因为我需要项目中的所有文件才能工作这些结构。

但是关于这个问题的最令人困惑的部分需要更多解释:

我在这个项目中设置这些多个文件的方式与我正在使用的书,John S. Harbour的All In One Game Programming相同。当我为书中的示例项目创建文件时,我遇到了完全相同的问题,这些项目要求在同一个项目中包含多个cpps的一个标题。

我可以从书中逐字逐句地输出它们,我的确逐字逐句...... 我会为项目中的每个cpp获得一系列MD错误。

如果我从本书附带的CD中加载了示例项目,它将编译并运行没有问题,尽管文件本身以及项目选项的外观与我创建的相同。

如果我创建了自己的项目文件,并且只是从CD中添加了示例项目的源文件和头文件,那么它也会编译并运行,但我发现这些文件和我的文件没有区别。

那么,我尝试制作我自己的项目文件,然后创建空白的源文件和头文件并将其添加到文件中,然后通过复制和粘贴CD中的文件来填充它们,这些文件是对应的(与之相同的那些)。 当然,我会得到同样的东西...... MD错误信息的行和行。

我绝对感到困惑。我已经多次重复所有这些方法,并且我确定我没有错误输入或错误复制代码。似乎有一些关于预制文件本身的东西;一些配置设置或其他我完全遗漏的东西...这将导致他们正确编译,而我自己制作的文件不会。

9 个答案:

答案 0 :(得分:22)

由于您在头文件中声明了这些变量,并在每个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行之前。这将照顾一切,并且(在我个人看来)比重新声明每个文件中的所有变量要好得多。

当然,真正的解决方案根本不是使用全局变量,而是当你刚刚开始时很难实现。

答案 1 :(得分:12)

定义变量时,编译器会为该变量留出内存。通过在头文件中定义一个变量,并将该文件包含在所有源文件中,您就可以在多个文件中定义相同的变量。

将关键字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];

答案 2 :(得分:6)

虽然大多数其他答案对于您看到多个定义的原因是正确的,但术语是不精确的。理解声明与定义是解决问题的关键。

声明宣布存在项目但不会导致实例化。因此,外部陈述是声明 - 而不是定义。

定义创建已定义项的实例。因此,如果您在标头中有定义,则会在每个.cpp文件中实例化,从而产生多个定义。定义也是声明 - 即,例如,如果项目的范围限于一个.cpp文件,则不需要单独的声明。

注意:这里使用单词instantiation实际上只适用于数据项。

答案 3 :(得分:4)

您需要在头文件中将变量定义为extern,然后在cpp文件中定义它们。即:

extern MYSTRUCT Job_Grunt;

在头文件中,然后在项目的cpp文件中正常声明它们。

头文件仅用于定义,当您在头文件中实例化变量时,每次在项目中包含头时,它都会尝试实例化它。使用extern指令告诉编译器它只是一个定义,并且实例化在其他地方完成。

答案 4 :(得分:2)

我还收到了.h文件中定义的函数的此错误。头文件不是用于声明类的声明,而是用于项目中各个位置所需的某些函数的定义。 (我可能会混淆“定义”和“声明”用法,但我希望我能提出主要想法。)当我在函数定义之前放置一个“内联”关键字时会产生“多个定义”错误,错误是避免的。

答案 5 :(得分:0)

为了扩展Gerald所说的内容,标题是定义结构的一个实例(这不是你想要的)。这导致包含头部的每个编译单元(cpp文件)获得自己的struct实例版本,这会在链接时导致问题。

正如Gerald所说,你需要在标题中定义对结构的引用(使用'extern'),并在项目中有一个实例化实例的cpp文件。

答案 6 :(得分:0)

我也有一段时间没有这个问题了。让我试着解释一下是什么解决了它。 我有一个global.h文件,它有所有声明,需要包含在每个cpp文件中。我把它包含在.h中,而不是将它包含在每个.cpp中。我的所有“.h”文件都添加了#ifndef和#define行,并以#endif结束。这解决了MD问题。希望这也适合你。

答案 7 :(得分:0)

这对我有用:将源链接到单独的库中。 (我的问题不是创建程序而是一个/多个库。)然后我将一个程序与我创建的两个库链接(成功)。

我在同一个源文件中有两组函数(一组依赖于另一组),并在同一个单独的头文件中声明。然后我尝试将两个函数集分成两个头文件+源文件。

我曾尝试使用#pragma一次,并使用#ifndef ... #define ... #endif包含警卫。我还在头文件中将变量和函数定义为extern。

史蒂夫·法洛斯指出,问题不在于汇编,而在于联系。在我的特定问题中,我可以使用两组函数,每个函数都在自己的源文件中,编译然后链接到两个独立的库

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联系起来。在我的特殊情况下,它没有任何区别;但除此之外我无法让他们一起工作。

答案 8 :(得分:-3)

GCC 3.4及以上支持#pragma once。只需将#pragma once放在代码顶部,而不是使用包含警戒。这可能会或可能不会更成功,但值得一试。不,这不是(总是)精确等同于包含守卫。