C++ 多重定义错误

2024-04-09

我的“Headers.h”文件包含基本的 C++ 标头

#include <iostream>
#include <cstring>
// and many header files.

编写了文件存在检查的函数定义并将其保存在“common_utility.h”中 - ifFileExist()

common_utility.h

bool ifFileExist()
{
   // ... My code
}

为A类编写代码 b类

class A
{
// Contains class A Declarations.

};

A类.cpp

// Contains
#include "Headers.h"
#include "common_utility.h"
#include "classA.h"

// class A Method definition

为B类编写代码 我在B类中使用A类。

classB.h

 class B
    {
// Contains class A Declarations.
}

B类.cpp

// Contains
#include "Headers.h"
#include "common_utility.h"
#include "classA.h"
#include "classB.h"

// class B Method definition
// calling the function ifFileExist() in class B also.

编写主程序代码 主程序

// Contains
#include "Headers.h"
#include "common_utility.h"
#include "classA.h"
#include "classB.h"

// I am using class A and Class B in main program
// calling the function ifFileExist() in Main program also.

当我将整个程序编译为

g++ -std=c++0x classA.cpp classB.cpp main.cpp -o main

我收到以下错误。

功能中ifFileExist()': classB.cpp:(.text+0x0): multiple definition ofifFileExist()' /tmp/ccHkDT11.o:classA.cpp:(.text+0x2b6e):首先在这里定义

所以我在 Headers.has extern 中声明了 if File Exist() 函数。

extern bool ifFileExist();

但我仍然遇到同样的错误。

我在每个 .cpp 文件中都包含“Headers.h”。该文件包含基本的 C++ 库。但我没有收到该头文件的任何多重定义错误。 但仅在我自己的函数中,我收到错误“多重定义”。

我想在需要使用“common_utility.h”文件时使用它。如果我不需要在主程序中使用 common_utility 函数,那么我就不应该包含它。

我希望我的程序在以下每种情况下运行。

g++ -std=c++0x classA.cpp main.cpp -o main
g++ -std=c++0x classB.cpp> main.cpp -o main
g++ -std=c++0x classA.cpp classB.cpp main.cpp -o main

在任何情况下我都不应该出现多重定义错误。我现在应该怎么做?


由于我找不到这个问题的任何完整的(在我看来)重复,我将写一个(希望)权威且完整的答案。

什么是单一定义规则以及我为什么要关心

单一定义规则(通常称为 ODR)是一种规则,它规定(简化)程序中使用的任何实体(非正式术语)都应该定义一次,并且只能定义一次。多次定义的实体通常会导致编译或链接器错误,但有时编译器可能无法检测到并导致非常难以跟踪的错误。

我不打算正式定义entity这里,但我们可以将其视为函数、变量或类。在进一步讨论之前,我们应该非常清楚地了解两者之间的区别定义 and 宣言在C++中,虽然禁止双重定义,但双重声明通常是不可避免的。

定义与声明

代码中使用的每个实体都应该是declared在给定的翻译单元中(翻译单元通常是一个cpp源文件以及其中包含的所有头文件,直接或间接通过其他头文件)。实体的声明方式因实体本身而异。请参阅下文了解如何声明不同类型的实体。实体通常在头文件中声明。由于大多数复杂的应用程序中都包含多个翻译单元(多个 cpp 文件),并且不同的 cpp 文件通常包含相同的标头,因此应用程序可能对所使用的许多实体有多个声明。正如我上面所说,这不是问题。

应用程序中使用的每个实体都必须是defined一次且仅一次。术语“应用程序”在这里使用得有点宽松 - 例如,库(静态和动态)可以在其中保留未定义的实体(此时通常称为符号),并且链接以使用动态库的可执行文件可以还有一个未定义的符号。相反,我指的是应用程序是终极运行的某物,在所有库都已静态或动态链接到其中并解析符号之后。

还值得注意的是,每个定义也都充当声明,这意味着,每当您定义某些内容时,您也在声明相同的事物。

与声明一样,定义实体的方式因实体类型而异。以下是如何根据实体的类型声明/定义 3 种基本类型的实体 - 变量、类和函数。

变量

变量使用以下结构声明:

extern int x;

这声明了一个变量x。它没有定义它!下面的代码将被编译正常,但尝试在没有任何其他输入文件的情况下链接它(例如,使用g++ main.cpp) 将由于未定义的符号而产生链接时错误:

extern int x;
int main() {
    return x;
}

下面这段代码defines变量 x:

int x;

如果将这一行放入文件 x.cpp 中,并且该文件与上面的 main.cpp 一起编译/链接g++ x.cpp main.cpp -o test它可以毫无问题地编译和链接。您甚至可以运行生成的可执行文件,如果您要在运行可执行文件后检查退出代码,您会注意到它是 0。(因为全局变量 x 将默认初始化为 0)。

功能

函数是通过提供其原型来声明的。典型的函数声明如下所示:

double foo(int x, double y);

该构造声明了一个函数foo,返回double并接受两个参数 - 一个类型int,另一种类型double。该声明可以出现多次。

以下代码defines上面提到的foo:

void foo(int x, double y) {
    return x * y;
}

This 定义在整个应用程序中只能出现一次。

与变量定义相比,函数定义还有一个额外的特点。如果上面的定义foo被放入头文件中foo.h,这又将包含在两个 cpp 文件中1.cpp and 2.cpp,它们被编译/链接在一起g++ 1.cpp 2.cpp -o test你会遇到链接器错误,说foo()被定义了两次。这可以通过使用以下形式来防止foo宣言:

inline void foo(int x, double y) {
    return x * y;
}

Note inline那里。它告诉编译器的是foo可以被多个.cpp文件包含,并且这个包含不应该产生链接器错误。编译器有多种选择来实现这一点,但可以依靠它来完成它的工作。请注意,在同一个翻译单元中两次使用此定义仍然会出现错误!例如,以下代码将产生编译器错误

inline void foo() { }
inline void foo() { }

值得注意的是,类中定义的任何类方法都是隐式内联的,例如:

class A {
public:
    int foo() { return 42; }
};

这里定义了 A::foo()inline.

Classess

类通过以下构造声明:

class X;

以上声明declaresX 类(此时 X 被正式称为不完整类型),以便当不需要有关其内容的信息(例如其大小或其成员)时可以使用它。例如:

X* p; // OK - no information about class X is actually required to define a pointer to it
p->y = 42; // Error - compiler has no idea if X has any member named `y`

void foo(X x); // OK - compiler does not need to generated any code for this

void foo(X x) { } // Error - compiler needs to know the size of X to generate code for foo to properly read it's argument
void bar(X* x) { } // OK - compiler needs not to know specifics of X for this

类的定义是众所周知的,并且遵循以下结构:

class X {
   public:
   int y;
};

这使得类 X 被定义,现在它可以在任何上下文中使用。重要的注意事项 - 类定义对于每个翻译单元必须是唯一的,但不必对于每个应用程序都是唯一的。也就是说,每个翻译单元只能定义一次 X,但它可以在链接在一起的多个文件中使用。

如何正确遵守 ODR 规则

每当在生成的应用程序中多次定义同一实体时,即所谓的ODR 违规发生了。大多数时候,链接器会发现违规行为并会抱怨。但是,在某些情况下,ODR 违规不会破坏链接,而是会导致错误。例如,当定义全局变量 X 的同一个 .cpp 文件被放入应用程序和动态库中时,可能会发生这种情况,该文件是按需加载的(使用dlopen)。 (你确实花了几天时间试图追踪因此而发生的错误。)

ODR 违规的更常见原因是:

同一实体在同一范围内的同一文件中定义两次

int x;
int x; // ODR violation

void foo() {
   int x;
} // No ODR violation, foo::x is different from x in the global scope

预防: 不要这样做。

同一个实体在应该声明的时候被定义了两次

(in x.h)
int x;

(in 1.cpp)
#include <x.h>
void set_x(int y) {
   x = y;
}

(in 2.cpp)
#include <x.h>
int get_x() {
    return x;
}

虽然上述代码的智慧充其量是值得怀疑的,但它在说明 ODR 规则方面起到了一定的作用。在上面的代码中,变量 x 应该在两个文件 1.cpp 和 2.cpp 之间共享,但编码不正确。相反,代码应该如下:

(in x.h)
extern int x; //declare x

(in x.xpp)
int x; // define x

// 1.cpp and 2.cpp remain the same

预防知道你在做什么。当您希望声明实体时声明它们,而不是定义它们。 如果在上面的示例中我们使用函数而不是变量,如下所示:

(in x.h)
int x_func() { return 42; }

我们会遇到一个可以通过两种方式解决的问题(如上所述)。我们可以使用inline函数,或者我们可以将定义移动到 cpp 文件中:

(in x.h)
int x_func();

(in x.cpp)
int x_func() { return 42; } 

相同的头文件包含两次,导致同一个类定义两次这是一个有趣的事情。想象一下,您有以下代码:

(in a.h)
class A { };

(in main.cpp)
#include <a.h>
#include <a.h> // compilation error!

上面的代码很少出现,但很容易在中间两次包含相同的文件:

(in foo.h)
#include <a.h>

(in main.cpp)
#include <a.h>
#include <foo.h>

预防传统的解决方案是使用所谓的包括警卫,即一个特殊的预处理器定义,可以防止双重包含。在这方面,a.h应该重做如下:

(in a.h)
#ifndef INCLUDED_A_H
#define INCLUDED_A_H

class A { };

#endif

上面的代码将防止将 a.h 多次包含到同一翻译单元中,因为INCLUDED_A_H将在第一次包含后定义,并且会失败#ifndef关于所有后续的。

一些编译器公开了其他方法来控制包含,但迄今为止,包含防护仍然是在不同编译器之间统一执行此操作的方法。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ 多重定义错误 的相关文章

随机推荐

  • MySQL列中的随机数[关闭]

    Closed 这个问题需要调试细节 help minimal reproducible example 目前不接受答案 我正在尝试在名为 likes 的数据库列之一中添加随机数 其中大约有 5000 行 目前我已经尝试像下面这样获得从 10
  • 我可以使用 PDO 参数化语句创建 MYSQL 表吗?

    我希望使用 PHP 和 PDO 创建一个 MySQL 表 我还希望参数化表名 我已经尝试实现这一点 并且有错误的代码如下所示 class databaseaccess public hostname localhost public use
  • 如何在C#中获得一组枚举值?

    假设我有一个枚举 http msdn microsoft com en us library system windows forms dialogresult aspx namespace System Windows Forms pub
  • 从 Firebase 检索的自定义对象始终具有 Null 属性

    我正在尝试检索自定义User来自 Firebase 的对象如下 getUserFromDB loggedInUserEmail viewModel new ViewModelProvider this get UserViewModel c
  • 无法将 SelectedIndex 设置为 0

    当尝试将 ComboBox SelectedIndex 设置为 0 以便我将第一个值作为默认值时 不起作用 组合框默认为空 如果我尝试将其设置为任何其他有效的数字 我会将所选索引作为默认值 有人知道为什么我不能从 xaml 将其设置为 0
  • 从 Ansible 中的自定义模块访问 playbook 变量[重复]

    这个问题在这里已经有答案了 我正在 Ansible 中编写一个特定于 Playbook 的自定义模块 是否可以直接访问剧本变量 而不需要将其作为参数传递给任务 这是不可能的 因为模块是远程执行的 除非显式传递 否则所有变量都不可用 我有同样
  • 从 X 和 Y 坐标获取纬度和经度

    看起来从经度和纬度到X和Y坐标的转换有丰富的知识 但反过来却没有 这是我根据 Kavrayskiy 的数学计算得出的函数 float xp kavraX radians pv x radians pv y FACTOR float yp k
  • 用字典重新映射 pandas 列中的值,保留 NaN

    我有一本字典 如下所示 di 1 A 2 B 我想将其应用到col1数据框的列类似于 col1 col2 0 w a 1 1 2 2 2 NaN to get col1 col2 0 w a 1 A 2 2 B NaN 我怎样才能最好地做到
  • C# 编组回调

    我正在尝试对结构中的 c 回调进行编组 我很确定我的一切都是正确的 但是当使用我的 C 示例时 我没有收到事件 而当使用 C 时 我确实收到了事件 这是 C class Program DllImport Some dll CharSet
  • django S3 - 修剪图像字段文件名但不修剪 url 路径

    这是我的问题的后续 ImageField FileField Django 表单当前无法修剪文件名的路径 https stackoverflow com questions 47887158 imagefield filefield dja
  • 将焦点设置回其父级?

    来自帖子WPF 如何以编程方式从文本框中删除焦点 https stackoverflow com questions 2914495 wpf how to programmatically remove focus from a textb
  • Number 类型的 DynamoDB 属性中可以存储多少位整数数据?

    DynamoDB 的Number https docs aws amazon com amazondynamodb latest developerguide HowItWorks NamingRulesDataTypes html How
  • 如何向 heroku 添加 API 密钥和其他安全内容?

    我在某处读过 但似乎无法找到将密钥添加到 Heroku 中的位置 而不需要将其放入源代码 git 存储库中 我想当我推送到 github 时这有助于保证它的安全 我该怎么做 这样做有意义吗 http docs heroku com conf
  • 如何在CKEditor中动态切换文本方向

    在我当前的项目中 用户可以用英语和希伯来语输入文本 根据当前文本自动定义方向会很棒 例如 如果文本包含希伯来语符号 则方向应为 RTL 但如果文本不包含希伯来语 则方向为 LTR 文本可以随时更改 我认为最好的解决方案是动态切换方向 就像在
  • 有没有办法强制 NHTMLUNIT 忽略页面 JavaScript 错误并继续脚本执行?

    我是 ASP NET 和 C 项目的一部分 我们正在努力使我们的 asp net 门户对 Google 搜索引擎友好 https developers google com webmasters ajax crawling https de
  • 将事件日志组织到文件夹中

    我想要创建多个服务 并且希望它们将每个服务记录在我指定的同一目录 文件夹下的日志条目中 这样当我打开 Windows 事件查看器时 我可以看到它们全部放置在一个文件夹中 例如 service1 将登录到 service1 log servi
  • jQuery 禁用/启用提交按钮

    我有这个 HTML
  • 在cmake中设置boost的最低版本

    我想定义系统上可用的最低增强版本 我尝试了以下方法 不幸的是 这不起作用 因为它尝试在系统上仅提供 boost 1 40 0 的情况下进行编译 SET Boost USE STATIC LIBS OFF SET Boost USE MULT
  • WPF/控制台混合应用程序

    我编写了一个可以在命令行上运行或使用 WPF UI 运行的应用程序 STAThread static void Main string args Does magic parse args and sets IsCommandLine to
  • C++ 多重定义错误

    我的 Headers h 文件包含基本的 C 标头 include