15

因此,无论我做什么,我似乎都无法避免 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 的开头读取包含时,它会重新定义头文件,这导致它吐出以下行和行:

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 文件看到一组这样的文件。我已经尝试将结构和结构变量的定义移动到 cpp 文件中,但是其他 cpp 文件看不到它们或无法使用它们,这非常重要,因为我需要项目中的所有文件才能工作使用这些结构。

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

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

我可以逐字逐句地从书中输入它们,而且我的意思是逐字逐句......
我会得到项目中每个 cpp 的一系列 MD 错误。

如果我从本书附带的 CD 中加载示例项目,它会毫无问题地编译和运行,尽管文件本身以及项目选项在所有外观上都与我创建的相同。

如果我创建了自己的项目文件,并简单地从 CD 中添加示例项目的源文件和头文件,这也将编译和运行,尽管我发现它们和我的没有区别。

因此,我尝试制作自己的项目文件,然后创建空白的源文件和头文件并将它们添加到其中,然后通过从 CD 上它们要对应的文件中复制和粘贴它们的内容来填充它们(相同那些已经工作的)。果然,我会得到同样的东西……一行又一行的 MD 错误消息。

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

4

9 回答 9

25

由于您在头文件中声明这些变量,并在每个 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台词之前。这将处理所有事情,并且(在我个人看来)比必须重新声明每个文件中的所有变量要好得多。

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

于 2008-10-21T22:36:05.027 回答
13

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

将关键字放在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];
于 2008-10-21T22:41:13.440 回答
6

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

声明宣布项目的存在,但不会导致实例化。因此, extern 语句是声明 - 而不是定义。

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

注意:这里使用的实例化这个词实际上只适用于数据项。

于 2008-10-22T01:21:37.830 回答
5

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

extern MYSTRUCT Job_Grunt;

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

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

于 2008-10-21T22:32:03.350 回答
2

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

于 2011-06-07T09:09:21.483 回答
0

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

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

于 2008-10-21T22:36:45.057 回答
0

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

于 2009-04-19T07:36:42.360 回答
0

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

我在同一个源文件中有两组函数(一组取决于另一组),并在同一个头文件中声明。然后我尝试将两个函数集分开在两个头文件+源文件中。

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

正如 Steve Fallows 所指出的,问题不在于编译,而在于链接。在我的特定问题中,我可以使用两组函数,每组函数都在自己的源文件中,编译然后链接到两个单独的库中。

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 链接起来。在我的特殊情况下,这没有什么区别;但否则我无法让他们一起工作。

于 2009-06-23T12:58:03.747 回答
-3

GCC 3.4 及更高版本支持#pragma once. 只需放在#pragma once代码的顶部,而不是使用包含防护。这可能会或可能不会更成功,但值得一试。不,这并不(总是)完全等同于包含防护。

于 2008-10-21T22:41:36.590 回答