11

我在标题中定义了一个字符数组

//header.h
const char* temp[] = {"JeffSter"};

如果 #defined 受到保护,并且顶部有一个 #pragma ,则标头。如果此标头包含在多个位置,我会得到一个 LNK4006 - char const * * temp 已经在 blahblah.obj 中定义。所以,我有几个关于这个的问题

  1. 如果我有防护装置,为什么会发生这种情况?我认为他们在第一次访问后阻止了标头被读入。
  2. 为什么此标头中的众多枚举也没有给出 LNK4006 警告?
  3. 如果我在签名之前添加静态,我不会收到警告。这样做有什么影响。
  4. 有没有更好的方法来避免错误,但让我在标题中声明数组。我真的很讨厌只为数组定义创建一个 cpp 文件。
4

5 回答 5

14

如果我有防护装置,为什么会发生这种情况?我认为他们在第一次访问后阻止了标头被读入。

包含守卫确保标题在一个文件(翻译单元)中只包含一次。对于包括标头的多个文件,您希望标头包含在每个文件中。

通过定义,而不是在头文件中声明具有外部链接的变量(全局变量),您只能在一次源文件中包含头文件。如果在多个源文件中包含头文件,则会有多个变量定义,这在 C++ 中是不允许的。

因此,正如您所发现的,正是出于上述原因,在头文件中定义变量是一个坏主意。

为什么此标头中的众多枚举也没有给出 LNK4006 警告?

因为,它们没有定义“全局变量”,它们只是关于类型的声明等。它们不保留任何存储空间。

如果我在签名之前添加静态,我不会收到警告。这样做有什么影响。

当您创建一个变量static时,它具有静态范围。该对象在定义它的翻译单元(文件)之外不可见。所以,简单来说,如果你有:

static int i;

在您的标头中,包含标头的每个源文件都将获得一个单独 int的变量i,该变量在源文件之外是不可见的。这称为内部链接

有没有更好的方法来避免错误,但让我在标题中声明数组。我真的很讨厌只为数组定义创建一个 cpp 文件。

如果您希望数组成为所有 C++ 文件中可见的一个对象,您应该这样做:

extern int array[SIZE];

在您的头文件中,然后将头文件包含在所有需要该变量的 C++ 源文件中array。在其中一个源 ( .cpp) 文件中,您需要定义array

int array[SIZE];

您还应该在上述源文件中包含标头,以便发现由于标头和源文件的差异而导致的错误。

基本上,extern告诉编译器“array在某处定义,并且具有 typeint和 size SIZE”。然后,您实际上只定义 array了一次。在链接阶段,一切都很好地解决了。

于 2010-01-20T23:17:07.530 回答
5
  1. 标题保护与防止整个程序中的多个定义完全无关。标头保护的目的是防止将同一个头文件多次包含到同一个翻译单元(.cpp 文件)中。换句话说,它们的存在是为了防止在同一个源文件中有多个定义。在您的情况下,它们确实按预期工作。

  2. 在 C++ 中管理多定义问题的规则称为单一定义规则 (ODR)。对于不同类型的实体,ODR 的定义不同。例如,允许类型在程序中具有多个相同的定义。它们可以(并且通常必须)在使用它们的每个翻译单元中定义。这就是您的枚举定义不会导致错误的原因。

    具有外部链接的对象是完全不同的故事。它们必须在一个且只有一个翻译单元中定义。这就是为什么temp当您将头文件包含到多个翻译单元中时,您的定义会导致错误。包括守卫不能防止这个错误。只是不要在头文件中定义具有外部链接的对象。

  3. 通过添加static你给你的对象内部链接。这将使错误消失,因为现在从 ODR 的角度来看它是完全可以的。但这将temp在包含您的头文件的每个翻译单元中定义一个独立的对象。为了达到同样的效果,你也可以这样做

    const char* const temp[] = { "JeffSter" }; 
    

    因为constC++ 中的对象默认具有内部链接。

  4. 这取决于您是否需要具有外部链接的对象(即,一个用于整个程序的对象)或具有内部链接的对象(对于每个翻译单元都是唯一的)。如果您需要后者,请使用static和/或额外const(如果对您有用),如上所示。

    如果您需要前者(外部链接),则应将非定义声明放入头文件中

    extern const char* temp[];
    

    并将定义移动到一个且只有一个 .cpp 文件中

    char* const temp[] = { "JeffSter" }; 
    

    头文件中的上述声明适用于大多数用途。然而,它声明temp为一个未知大小的数组——一个不完整的类型。如果您希望将其声明为已知大小的数组,则必须手动指定大小

    extern const char* temp[1];
    

    并记住在声明和定义之间保持同步。

于 2010-01-20T23:16:35.270 回答
4

包含守卫可防止您将相同的标头重复包含到同一文件中 - 但不会将其包含在不同的文件中。
发生的情况是链接器temp在多个目标文件中看到 - 您可以通过temp将其设为静态或将其放入未命名的命名空间来解决该问题:

static const char* temp1[] = {"JeffSter"};
// or
namespace {
    const char* temp2[] = {"JeffSter"};
}

或者,您可以使用一个源文件来定义temp并在标头中将其声明为 extern:

// temp.cpp:
const char* temp[] = {"JeffSter"};

// header.h:
extern const char* temp[];
于 2010-01-20T23:08:04.010 回答
0

我恭敬地不同意在标题中定义变量的建议,因为我认为“从不”太宽泛了。尽管如此,将我带到这个主题的那一集为那些敢于这样做的人提供了一个警示故事。

作为对警告 LNK4006 原因的调查的结果,我登陆了这个页面,调用了一个长期建立的数组,我刚刚从定义我的 DLLMain 例程的翻译单元移动到大多数翻译单元中包含的私有头文件中组成这个库。在过去的 11 年里,我已经编译了数百次这个库,而且我以前从未见过这个警告。

阅读此页面后不久,我发现了错误的原因,即定义位于保护块之外,该保护块保护了模块中定义的所有其他内容,该模块还定义了 DLLMain,我通常在这里收集所有内存块需要外部链接的。正如预期的那样,将表移动到保护块内消除了警告,只剩下两个与全新的外部链接表相关的警告需要解决。

要点:您可以在标题中定义变量,这是放置公共块的好地方,但请注意您的警卫。

于 2016-07-16T09:05:54.167 回答
-2

等等……你在混淆你的声明……你确实说过'char const * * temp'但在你的头文件中你有'const char* temp[] = {“JeffSter”};'。

请参阅C FAQ的第 6.1节,在“第 6 节。数组和指针”下,引用:

6.1:我在一个源文件中有定义 char a[6],在
    另一个我声明了 extern char *a。为什么它不起作用?

答:在一个源文件中,您定义了一个字符数组,并且在
    其他你声明了一个指向字符的指针。宣言
    extern char *a 根本不符合实际定义。
    指向类型 T 的类型指针与类型 T 的数组不同。
    使用 extern char a[]。

    参考文献:ISO Sec。6.5.4.2;CT&P 秒。3.3 第 33-4 页,秒。4.5
    第 64-5 页。

这就是问题的根源。匹配您的声明和定义。对不起,如果这听起来很生硬,但我不禁注意到链接器告诉你什么......

于 2010-01-20T23:12:13.160 回答