【问题标题】:Global variables in header file头文件中的全局变量
【发布时间】:2011-12-27 20:40:41
【问题描述】:

我有 2 个模块(.c 文件)和一个 .h 头文件:

file1.c:

#include <stdio.h>
#include "global.h"

int main()
{
    i = 100;
    printf("%d\n",i);
    foo();
    return 0;
}

file2.c

#include <stdio.h>
#include "global.h"

void foo()
{
    i = 10;
    printf("%d\n",i);
}

global.h

int i;
extern void foo()

当我执行 gcc file1.c file2.c 时,一切正常,我得到了预期的输出。现在,当我在头文件中将变量“i”初始化为 0 并再次编译时,我得到一个链接器错误:

/tmp/cc0oj7yA.o:(.bss+0x0): multiple definition of `i'
/tmp/cckd7TTI.o:(.bss+0x0): first defined here

如果我只是编译 file1.c(删除对 foo() 的调用)并在头文件中初始化,即 gcc file1.c,一切正常。怎么回事?

【问题讨论】:

    标签: c


    【解决方案1】:

    您描述的有 3 个场景:

    1. 包含 2 个 .c 文件,并且在标题中包含 int i;
    2. 有 2 个 .c 文件和标题中的 int i=100;(或任何其他值;没关系)。
    3. 包含 1 个 .c 文件和标题中的 int i=100;

    在每种情况下,想象头文件的内容插入到.c 文件中,并将此.c 文件编译为.o 文件,然后将它们链接在一起。

    然后发生以下情况:

    1. 工作正常,因为已经提到了“暂定定义”:每个.o 文件都包含其中一个,所以链接器说“ok”。

    2. 不起作用,因为两个 .o 文件都包含一个带有值的定义,它们会发生冲突(即使它们具有相同的值) - 在所有 .o 中可能只有一个具有任何给定名称在给定时间链接在一起的文件。

    3. 当然可以,因为您只有一个.o 文件,因此不可能发生冲突。

    恕我直言,一件干净的事情是

    • extern int i; 或仅int i; 放入头文件中,
    • 然后将i的“真实”定义(即int i = 100;)放入file1.c。在这种情况下,该初始化在程序开始时使用,main() 中的相应行可以省略。 (另外,我希望命名只是一个例子,请不要在实际程序中将任何全局变量命名为i。)

    【讨论】:

    • 我所说的“将extern int i; 或只是int i; 放入头文件中”:extern int i 更好,因为它会立即告诉您是否意外丢失了“真实”定义.只需 int i,就会有对 0 的静默定义。
    • 我没有得到第一个解释,您能否详细说明一下何时将内存分配给变量 i。到目前为止,我理解的是 global.h 中的 int i 相当于 extern int i;这意味着两个目标文件都具有对 i 内存的引用,该内存分配在其他地方。
    • @user2383973 内存由链接器分配和保留。当它看到一个带有分配的请求和一个“暂定”定义时,一切都很好。当它看到几个赋值时,即使它们的值相同,也不行。
    • @FoadRezek 我假设您尝试了问题中的文件结构。这是因为分配在文件级别无效,仅在函数内部有效。
    • case 1 是未定义的行为,暂定定义会导致为其出现的每个翻译单元生成一个外部定义。 (C11 6.9.2/2)。你没有extern的推荐也有同样的问题
    【解决方案2】:

    不要初始化标头中的变量。将声明放在标题中,并将初始化放在 c 文件之一中。

    在标题中:

    extern int i;
    

    在file2.c中:

    int i=1;
    

    【讨论】:

    • 请注意,OP 并未初始化变量,它是一个暂定定义。
    • @Banthar:那为什么它在第二种情况下起作用(当我只编译 file1.c 时)?
    • @Bruce:因为在这种情况下,它只初始化了一次。
    【解决方案3】:

    您不应在头文件中定义全局变量。您可以在头文件中将它们声明为extern,并在.c 源文件中定义它们。

    (注意:在 C 中,int i; 是一个暂定定义,如果在翻译单元中没有为该变量找到其他定义,它会为该变量分配存储空间(= 是一个定义)。

    【讨论】:

    • 如果我将#ifndef 放在标题中并在标题中声明变量int a 会怎样?尽管现在我只有一个头文件的副本,但仍然会出现多重定义错误。
    【解决方案4】:

    这个问题的currently-accepted answer 是错误的。 C11 6.9.2/2:

    如果翻译单元包含一个或多个暂定定义 标识符,并且翻译单元不包含该标识符的外部定义,则行为与翻译单元包含该标识符的文件范围声明完全相同,复合类型截至翻译单元的末尾,初始值设定项等于为 0。

    所以问题中的原始代码表现得好像file1.cfile2.c 都在末尾包含int i = 0; 行,这会由于多个外部定义(6.9/5)而导致未定义的行为。

    由于这是语义规则而不是约束,因此不需要诊断。

    这里还有两个关于相同代码的正确答案的问题:

    【讨论】:

      【解决方案5】:

      @glglgl 已经解释了为什么您尝试做的事情不起作用。实际上,如果你真的想在头文件中定义一个变量,你可以使用一些预处理器指令来欺骗:

      file1.c:

      #include <stdio.h>
      
      #define DEFINE_I
      #include "global.h"
      
      int main()
      {
          printf("%d\n",i);
          foo();
          return 0;
      }
      

      file2.c:

      #include <stdio.h>
      #include "global.h"
      
      void foo()
      {
          i = 54;
          printf("%d\n",i);
      }
      

      global.h:

      #ifdef DEFINE_I
      int i = 42;
      #else
      extern int i;
      #endif
      
      void foo();
      

      在这种情况下,i 仅在您定义 DEFINE_I 的编译单元中定义,并且在其他任何地方声明。链接器不会抱怨。

      在头文件中声明枚举之前,我已经看过几次了,下面是包含相应标签的 char** 的定义。我确实理解为什么作者更喜欢在头文件中使用该定义而不是将其放入特定的源文件中,但我不确定实现是否如此优雅。

      【讨论】:

        【解决方案6】:

        不要在头文件中定义变量,在头文件中声明(良好做法)..在你的情况下它是有效的,因为多个弱符号..阅读弱符号和强符号..链接:http://csapp.cs.cmu.edu/public/ch7-preview.pdf

        这种类型的代码在移植时会产生问题。

        【讨论】:

        • 你想说什么
        • @tod。不是最好的答案,但如果你想知道他的定义/声明是什么意思,这里是你应该做什么的格式正确的答案stackoverflow.com/a/1164190/310560
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-29
        • 1970-01-01
        相关资源
        最近更新 更多