【问题标题】:Linker error for multiply defined symbols多重定义符号的链接器错误
【发布时间】:2016-08-10 02:10:09
【问题描述】:

我的文件结构如下:

X.h

#pragma once

#include "Y.h"

int ONE = 1;

int combine();

X.c

#include "X.h"

int combine()
{
    return ONE + TWO;
}

Y.h

#pragma once

int TWO = 2;

Y.c

#include "Y.h"

Main.c

#include "X.h"

int main()
{
    int fusion = combine();

    return 0;
}

我收到以下错误:

LNK1169 one or more multiply defined symbols found

LNK2005 _ONE already defined in Main.obj

LNK2005 _TWO already defined in Main.obj

KLNK2005 _TWO already defined in Main.obj

这是零意义。如果我们从Main.c 开始,编译器必须包含X.h。然后编译器查找与X.h 关联的C 文件。在X.c 中,它需要包含X.h,但#pragma once 应该提防这种情况。然后它需要包含Y.h。它查找 C 文件并找到 Y.c,它说包含 Y.h,但 Y.h 已经包含在内。然后,它返回到 Main.c 并且应该成功编译......但是没有。

我可以将/FORCE 添加到我的项目设置中,这使我的代码可以完美运行,但仍会输出:

ONE has already been defined, second definition ignored.

【问题讨论】:

  • 然后编译器查找与 X.h 关联的 C 文件。 -- 什么?你是从哪里想到这个的?
  • 我不知道从哪里开始解开你对 w.r.t 头文件的所有错误。你陈述了你认为发生的事情,而这些事情都没有发生。编译器不会搜索其他 C 模块以查看它们包含的内容。
  • @Hatefiend 不。您告诉编译器要编译哪些.c 文件 - 直接在命令行上或通过像 Make 这样的构建系统。编译器不会根据头文件自动查找.c 文件。至于 pragma once,它可以防止在 single 源文件中多次包含。它不会阻止包含来自 不同 源文件的相同头文件。后者是您所拥有的,也是导致多定义链接错误的原因。
  • @Hatefiend 这不是嘲笑。你提出了一个完全错误的场景,鉴于此,我们需要从一开始就对构建过程、头文件等进行解释。
  • 一般来说,每当您询问编译或链接问题时,如果您还提供有关平台和编译器的信息,以及有关构建的任何可能非典型的信息(例如符号链接),它会有所帮助。我严重怀疑这里的情况,pragma once 很常见,但严格来说不是标准的一部分,预编译链如何实现文件识别也不是(例如,如果它使用文件标识而不是解析它可以被打破符号链接,而 ifdef 守卫不会)。

标签: c


【解决方案1】:

#pragma once 应该提防这种情况

#pragma once 用于避免重复包含在单个翻译单元(本例中为.c 文件)中,但它不能防止多个定义跨越多个翻译单元。

您在.h文件中定义了全局变量ONETWO,它们被包含在多个翻译单元中并导致多个定义错误。

例如,最后,ONE 将在X.cMain.c 中定义,TWO 将在Y.cX.cMain.c 中定义。

您应该在.h 文件中声明ONETWO(使用extern),并在.c 文件中定义它们。

X.h

#pragma once

#include "Y.h"

extern int ONE;
int combine();

X.c

#include "X.h"

int ONE = 1;

int combine()
{
    return ONE + TWO;
}

Y.h

#pragma once

extern int TWO;

Y.c

#include "Y.h"

int TWO = 2;

【讨论】:

  • 可能值得将“#pragma once 用于重复包含”扩展为“#pragma once 用于在单个编译单元中重复包含(在本例中为 .c 文件)”
  • @user4581301 是的,这很重要。
  • 在什么情况下我会有一个翻译单元?那是我只有一个文件,还是只有.c 文件?
  • @Hatefiend 在您的代码中,您有三个编译单元。你编译三个 .c 文件,然后链接它们,对吗?此处链接时出现多重定义错误。
  • 好吧,你说#pragma once 仅在它是单个翻译单元时保护我。这意味着为了真正保护我,我必须尝试以某种方式#include 与我已经在其中的.c.h 文件相同?什么时候会发生或什么时候有用?
【解决方案2】:

#pragma once 只防止同一个头文件在同一个编译单元中包含两次。当您在多个编译单元中定义相同的变量时,它不会防止出现问题。

要看看有什么问题,让我们来看看每个编译单元在编译时的样子。

经过预处理后,X.c 最终看起来像这样:

int TWO = 2;

int ONE = 1;

int combine();

int combine()
{
    return ONE + TWO;
}

如您所见,它包含符号TWOONEcombine 的定义。

Y.c 最终看起来像这样:

int TWO = 2;

它还定义了符号TWO

main.c 看起来像这样:

int TWO = 2;

int ONE = 1;

int combine();

int main()
{
    int fusion = combine();

    return 0;
}

所以它还定义了TWOONE,以及main

所以编译后,我们最终得到了 3 个目标文件,这三个目标文件都有TWO 的定义,其中两个有ONE 的定义。当我们开始链接时,链接器会在combine 的定义中看到对ONETWO 的引用,然后去寻找这些符号的定义。由于它们是在多个位置定义的,因此会引发错误并放弃。

您可以通过在 X.h 和 Y.h 中声明 ONETWO 以及在 X.c 和 Y.c 中定义 ONETWO 来解决此问题。 p>

【讨论】:

  • 感谢您的回复。所以在这里我要排除一些可能性。我可以 A)在我制作的每个 .h 文件中使用标题保护,并且 not 使用 #pragma once B)我可以按照你说的做,并定义我在每个文件中创建的每个全局变量单个 .h 文件(如果我在单个编译单元中有 100 多个文件,这听起来很糟糕),或者 C)我可以标记我创建的每个全局变量 extern 这听起来也很痛苦,因为最初我可能会写很多变量不需要被另一个文件使用,然后决定我以后真的需要。我说的对吗?
  • 一般来说,你只能在你的头文件中声明全局变量,并在一个单独的 .c 文件中定义它们。使用#ifndef 标头保护不会解决您的问题,并且在每个头文件中定义您的全局变量只会让事情变得更糟。一般来说,每个符号在你的程序中必须只有一个定义,并且在包含在多个 .c 文件中的头文件中定义一个全局将导致该符号被定义多次。
  • 我还不太了解你。如果我在一个头文件中定义全局变量int x = 5,这就是我的一个定义。现在,如果任何其他翻译单元中的任何其他文件进入该.h 文件,标头守卫说不,他们只是离开文件而不读取任何文件,对吗?那么就不需要externpragma once 或在十亿个不同的文件中输入相同的变量
  • 头文件的内容被简单地复制/粘贴到#includes它的任何文件中。头文件不是单独编译的,也没有任何东西“进入”头文件。在我的回答中,我已经准确地展示了每个翻译单元对编译器的外观。它什么也看不见。
  • 这是另一件事。我正在阅读this post,人们基本上是在暗示#pragma once 和标题后卫几乎完全相同。然而,头球后卫有能力解决我在原帖中的问题,不是吗?他们可以处理来自多个翻译单元的保护吗?”
【解决方案3】:

#pragma once 防止在编译单个源模块时多次包含头文件。

就是这样。

您的头文件被包含在两个不同的源模块中。

该头文件在全局范围内声明并定义了一个对象。

因此,您的两个源模块最终都会在全局范围内定义相同的对象。

这就是重复符号定义的来源。

#pragma once 不能替代了解和理解 C++ 范围规则。

【讨论】:

  • 在这种情况下,我使用的是 C,但我认为建议是一样的?我觉得这很奇怪,因为 Microsoft Visual Studio 会自动将 #pragma once 放在每个 .h 文件的开头。我认为这是程序告诉我我受到保护而不是重新定义的方式。另外,你什么时候会有一个单一的源模块?我在每一个 C/C++ 程序中都使用.h 文件,因为我被教导这是一种很好的做法。这是否意味着单个源模块 = #pragma once 和多个源模块 = header guards
  • 不管是 C 还是 C++。是的,它防止仅在同一源模块中重新定义。您无法理解“它不保护任何东西以防止在多个源模块中重新定义”的哪一部分?没有办法防止在多个模块中重新定义。定义“int a=0;”在一个模块中,并且“int b = 0;”在另一个,你有多个定义。编译器一次编译一个源模块。它无法知道在其他源模块中可能声明或不声明什么。停止使用 pragma 作为拐杖。了解范围界定的工作原理。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-07-19
  • 1970-01-01
  • 1970-01-01
  • 2012-05-08
  • 2018-11-23
相关资源
最近更新 更多