常量变量在头文件中不起作用

时间:2010-02-24 18:57:55

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

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

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;

它有效

有没有人知道我可能做错了什么?

由于

11 个答案:

答案 0 :(得分:127)

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

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

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

    在C中需要明确的static

    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. 或者您可以将非定义声明放入头文件中,并将 definitions 放入一个(且只有一个)实现文件中

    标头文件中的声明必须包含明确的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是可选的。)

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

    第一种方法使编译器更容易优化代码,因为它可以在每个转换单元中看到常量的实际值。但同时在概念上你会在每个翻译单元中获得独立的独立常量对象。例如,&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 :(得分:9)

extern表示变量的“真实”定义在别处,编译器应该相信事物会在链接时挂钩。将定义与extern内联在一起是很奇怪的,并且正在改变你的程序。如果您希望将它们设为extern,只需在程序的其他位置完全定义

答案 2 :(得分:5)

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

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

在C中,extern相当不同,删除extern不会产生任何真正的区别,因为默认情况下它们会extern。在这种情况下,您确实需要在一个位置初始化变量,并在标题中将它们声明为extern。或者,如果您从标题中删除static,则可以添加C ++默认添加的extern存储类。

答案 3 :(得分:2)

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

您需要在头文件中使用 - 定义声明(无初始化程序),并将定义声明放在实现文件的一个中。

答案 4 :(得分:2)

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

因为这些被声明为const,所以在头文件中有定义是没有问题的。 C ++将为内置类型内联一个const,除非你试图获取它的地址(一个指向const的指针),在这种情况下它将使用static链接实例化它,你也可以在单独的模块中获得多个实例化,但除非你希望所有指向同一个const的指针具有相同的地址,否则这不是问题。

答案 5 :(得分:1)

您需要在标头中声明容器,然后在其中一个代码文件中定义它们。如果您未在任何地方声明它们,则在尝试将声明与实际定义绑定时会出现链接器错误。您还可以使用#ifdef语句在标题中包含一个定义。

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

雅各

答案 6 :(得分:1)

如果要在头文件中定义常量,请使用static const。如果您使用extern,链接器会抱怨多个定义,因为如果您指定了值,每个包含源文件将为变量提供内存。

答案 7 :(得分:1)

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

在每个头文件的顶部,你应该有:

#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;

答案 8 :(得分:0)

在头部中声明全局const会导致包含此hader的每个编译单元具有自己的定义全局定义具有相同的名称。 然后链接器不喜欢它。

如果你真的需要这些标题,那么你可能应该将它们声明为静态。

答案 9 :(得分:0)

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

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

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

template <typename Dummy = int>
const double C<Dummy>::Pi = 3.14159;

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

答案 10 :(得分:0)

我也遇到了这个问题:

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

干杯 约翰