【问题标题】:constant variables not working in header常量变量在标头中不起作用
【发布时间】:2011-01-20 16:23:50
【问题描述】:

如果我像这样在标题中定义常量变量...

extern const double PI = 3.1415926535;
extern const double PI_under_180 = 180.0f / PI;
extern const double PI_over_180 = PI/180.0f;

我收到以下错误

1>MyDirectX.obj : error LNK2005: "double const PI" (?PI@@3NB) already defined in main.obj
1>MyDirectX.obj : error LNK2005: "double const PI_under_180" (?PI_under_180@@3NB) already defined in main.obj
1>MyDirectX.obj : error LNK2005: "double const PI_over_180" (?PI_over_180@@3NB) already defined in main.obj
1>MyGame.obj : error LNK2005: "double const PI" (?PI@@3NB) already defined in main.obj
1>MyGame.obj : error LNK2005: "double const PI_under_180" (?PI_under_180@@3NB) already defined in main.obj
1>MyGame.obj : error LNK2005: "double const PI_over_180" (?PI_over_180@@3NB) already defined in main.obj

但是如果我从标题中删除这些常量并将它们放入包含这样标题的文档中......

const double PI = 3.1415926535;
const double PI_under_180 = 180.0f / PI;
const double PI_over_180 = PI/180.0f;

有效

有人知道我做错了什么吗??

谢谢

【问题讨论】:

  • 你应该写180.0而不是180.0f,因为你处理的是双精度而不是浮点数。还将PI 重命名为更独特的名称。 PI 在许多库中用作宏,如果使用它,可能会得到奇怪的结果。
  • 失去extern,你会没事的。
  • @sellibitze:不在 C 中,const 对象默认具有外部链接,这意味着 extern 不会改变任何东西。

标签: c++ c visual-studio visual-studio-2008 visual-c++


【解决方案1】:

问题是您在头文件中定义 具有外部链接的对象。预计,一旦将该头文件包含到多个翻译单元中,您将获得具有外部链接的同一对象的多个定义,这是一个错误。

正确的做法取决于你的意图。

  1. 您可以将定义放入头文件中,但要确保它们具有内部链接。

    在需要显式 static 的 C 语言中

    static const double PI = 3.1415926535; 
    static const double PI_under_180 = 180.0f / PI; 
    static const double PI_over_180 = PI/180.0f; 
    

    在 C++ 中 static 是可选的(因为在 C++ 中 const 对象默认具有内部链接)

    const double PI = 3.1415926535; 
    const double PI_under_180 = 180.0f / PI; 
    const double PI_over_180 = PI/180.0f; 
    
  2. 或者你也可以把非定义的声明放到头文件中,然后把定义放到一个(并且只有一个)实现文件中

    header 文件中的声明必须包含明确的extern无初始化程序

    extern const double PI; 
    extern const double PI_under_180; 
    extern const double PI_over_180; 
    

    一个实现文件中的定义应如下所示

    const double PI = 3.1415926535; 
    const double PI_under_180 = 180.0f / PI; 
    const double PI_over_180 = PI/180.0f; 
    

    (如果上述声明在同一翻译单元中的定义之前,则定义中的显式extern 是可选的)。

您将选择哪种方法取决于您的意图。

第一种方法使编译器更容易优化代码,因为它可以看到每个翻译单元中常量的实际值。但同时从概念上讲,您在每个翻译单元中都获得了单独的、独立的常量对象。例如,&PI 将评估为每个翻译单元中的不同地址。

第二种方法创建真正的全局常量,即整个程序共享的唯一常量对象。例如,&PI 将评估为每个翻译单元中的相同地址。但在这种情况下,编译器只能在一个且只有一个翻译单元中看到实际值,这可能会妨碍优化。


从 C++17 开始,您将获得第三个选项,它结合了“两全其美”:内联变量。尽管有外部链接,但内联变量可以安全地在头文件中定义

inline extern const double PI = 3.1415926535; 
inline extern const double PI_under_180 = 180.0f / PI; 
inline extern const double PI_over_180 = PI/180.0f; 

在这种情况下,您将获得一个命名常量对象,其初始化值在所有翻译单元中都是可见的。同时该对象具有外部链接,即它具有全局地址标识(&PI 在所有翻译单元中都是相同的)。

当然,类似的东西可能只在某些特殊用途中是必需的(C++ 中的大多数用例都需要第一个变体),但该功能就在那里。

【讨论】:

  • +1。只是想提一下,在 C 中,static const 变量不是一个常量表达式,例如您需要的数组大小。我想这就是为什么#defines 在 C 语言中更受欢迎的原因。
  • +1 以获得良好的描述并教给我一些我不知道的东西:“在 C++ 中,常量对象默认具有内部链接”
  • @sellibitze:是的,但这主要是整数常量的问题。对非整数常量对象的影响可以忽略不计,如果存在的话。
  • 喜欢在 Stackoverflow 上找到通常比 doco 更好读的完整答案。
【解决方案2】:

extern 表示变量的“真实”定义在别处,编译器应该相信事情会在链接时连接起来。将定义与extern 内联是很奇怪的,这会影响您的程序。如果您想让它们成为extern,只需在程序的其他位置只定义一次

【讨论】:

    【解决方案3】:

    他们的extern 存储类几乎肯定是您看到的问题的原因。如果你删除它,代码可能会很好(至少在这方面)。

    编辑:我刚刚注意到您已将其标记为 C 和 C++。在这方面,C 和 C++ 确实有很大不同(但从错误消息来看,您显然是在编译为 C++,而不是 C)。在 C++ 中,您想要删除 extern,因为(默认情况下)const 变量具有 static 存储类。这意味着每个源文件(翻译单元)都将获得自己的变量“副本”,并且不同文件中的定义之间不会有任何冲突。由于您(可能)仅使用这些值,而不是将它们视为变量,因此拥有多个“副本”不会有任何损害——它们都不会被分配存储空间。

    在 C 中,extern 相当不同,删除 extern 不会有任何实际区别,因为默认情况下它们将是 extern。在这种情况下,您确实需要在一个地方初始化变量,并在标头中将它们声明为 extern。或者,您可以添加 static 存储类,当/如果您从标头中删除 extern 时,C++ 将默认添加该存储类。

    【讨论】:

      【解决方案4】:

      问题是你正在初始化头文件中的变量;这会创建一个 defining 声明,该声明在每个包含该标题的文件中重复,因此会出现多重定义错误。

      你想要一个定义的声明(没有初始化器)在头文件中,并将定义声明放在实现文件的一个中。

      【讨论】:

      • 如果我们在头文件的开头使用#ifndef #define 并且仍然define 常量,那还不够吗?
      • @quantum231:你的意思是,使用宏而不是const 变量?
      【解决方案5】:

      下面有很多不正确的回答。正确的是那些告诉您删除extern 的人,因为sellibitze 在他的评论中也说过是正确的。

      因为这些被声明为 const,所以在标题中定义没有问题。 C++ 将为内置类型内联 const,除非您尝试获取其地址(指向 const 的指针),在这种情况下,它将使用 static 链接对其进行实例化,然后您还可以在单​​独的模块中获得多个实例化,但除非你希望所有指向同一个 const 的指针都具有相同的地址,这不是问题。

      【讨论】:

      • MSVC 的行为与您的预期不同。如果在标头中将变量声明为 const,则可能会得到重复的符号。您需要同时使用static const 以确保内部链接。也许这是一个编译器错误。
      • @jww :在问题中,据报道它在没有静态的情况下工作。 C 的行为 确实 不同,但由于相关代码链接,它必须使用 C++ 编译。然而,这个问题同时被标记为 C 和 C++,所以可以说我应该涵盖两者,但问题不是关于 C 和 C++ 之间的差异,所以我假设只是被错误标记了。该问题已有 7 年历史,因此不需要更新。
      【解决方案6】:

      您需要在标头中声明内容,然后在您的代码文件之一中定义它们。如果您没有在任何地方声明它们,那么当它试图将声明与实际定义联系起来时就会出现链接器错误。您还可以使用#ifdef 语句在标题中包含一个定义。

      确保它们在需要它们的每个人都包含的标头中声明,并确保它们只定义一次。

      雅各布

      【讨论】:

        【解决方案7】:

        如果要在头文件中定义常量,请使用static const。如果您使用extern,链接器抱怨多个定义是正确的,因为如果您分配一个值,每个包含源文件都会为变量提供内存。

        【讨论】:

          【解决方案8】:

          看起来头文件被多次包含。您需要添加警卫。

          在每个头文件的顶部你应该有这样的东西:

          #ifndef MY_HEADER_FILE_NAME_H
          #define MY_HEADER_FILE_NAME_H
          
          ...
          
          // at end of file
          #endif
          

          如果您使用的是 g++ 或 MSVC,那么您只需添加:

          #pragma once
          

          在每个头文件的顶部,但这不是 100% 可移植的。

          另外,你不应该在头文件中定义常量,只声明它们:

          // In header file
          extern const int my_const;
          
          
          // In one source file
          const int my_const = 123;
          

          【讨论】:

          • 包含防护仅防止在同一文件中多次包含。缺少它们会在编译时产生问题。他在链接时遇到了问题,因为他试图在多个文件中分别定义相同的符号。
          • 我在回答中提到了这一点。
          【解决方案9】:

          在 header 中声明 global const 会导致包括此 hader 在内的每个编译单元都将具有自己的定义具有相同名称的全局定义。 然后链接器不喜欢那样。

          如果您真的需要在标头中使用这些,那么您可能应该将它们声明为静态。

          【讨论】:

            【解决方案10】:

            确实是一个老问题,但缺少一个有用的答案。

            可以通过将静态常量包装在“虚拟”类模板中来欺骗 MSVC 接受标题中的静态常量:

            template <typename Dummy = int>
            struct C {
                 static const double Pi;
            };
            
            template <typename Dummy = int>
            const double C<Dummy>::Pi = 3.14159;
            

            现在,可以从其他地方访问 C::PI。没有重新定义的抱怨;常量可以在每个编译单元中直接访问,无需花哨的链接时间优化。可以推出宏来进一步美化这种方法(即使宏是邪恶的)。

            【讨论】:

            • 使用这种方法有什么缺点吗?
            • 如前所述,有一点语法上的缺点 - 在整个代码中必须将 Pi 称为 C::Pi。但是,无论如何,最好避免松散的全局常量(因此 MathConst::Pi 只比 MathConst::Pi 差一点)。
            【解决方案11】:

            我也遇到过这个问题:

            static const uint64 GameTexSignature = 0x0a1a0a0d58455489;
            

            在头文件中定义时不会在 Linux 上编译。它与 MSVC 编译得很好。对我有用的修复方法是将其更改为:

            static constexpr uint64 GameTexSignature = 0x0a1a0a0d58455489;
            

            这只需要 uint64 常量,而不是任何 uint32 常量。我认为下面解释了它,但这确实意味着 Linux 编译器不认为 const uint64 是一个整数常量。

            https://exceptionshub.com/how-to-declare-a-static-const-char-in-your-header-file.html

            干杯 约翰

            【讨论】:

              猜你喜欢
              • 2014-11-07
              • 2019-02-20
              • 1970-01-01
              • 2018-04-03
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-03-25
              • 1970-01-01
              相关资源
              最近更新 更多