试图理解 C 中的 extern

Trying to understand extern in C

根据 here 给出的答案,我试图构建一个工作示例并尝试理解外部 linkage 的行为。

这是我的例子:

extern.h

extern int global_counter;

int file_counter;

extern.c

#include "extern.h"

int global_counter = 5;

incrementor1.c

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

void incrementor1_global()
{
    global_counter++;
    printf("From Incrementor1...\n");
    printf("Global counter is: %d\n", global_counter);
}

void incrementor1_local()
{
    file_counter++;
    printf("From Incrementor1...\n");
    printf("File counter is: %d\n", file_counter);
}

incrementor2.c

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

void incrementor2_global()
{
    global_counter++;
    printf("From Incrementor2...\n");
    printf("Global counter is: %d\n", global_counter);
}

void incrementor2_local()
{
    file_counter++;
    printf("From Incrementor2...\n");
    printf("File counter is: %d\n", file_counter);
}

您可以自己将它们放入单独的文件中,如图所示。这是我的 main.c 现在:

main.c

void incrementor1_global();
void incrementor1_local();

void incrementor2_global();
void incrementor2_local();

int main(char argc, char* argv[])
{
    incrementor1_global();
    incrementor2_global();

    incrementor1_local();
    incrementor2_local();
}

因为我 link .o 文件到我的 main,我只是声明函数,因为它们默认是外部的,它们被 linker link编辑为我明白了。最后,如果您想自己尝试,这里有一个 makefile。

CC=gcc

CFLAGS=-I.

DEPS = extern.h

OBJ = main.o extern.o incrementor1.o incrementor2.o  

%.o: %.c $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS)

main: $(OBJ)
    gcc -o $@ $^ $(CFLAGS)


.PHONY: clean

clean:
    rm -f *.o  

所以根据上面 link 的回答,我认为 global_counter 应该由所有翻译单位共享,而每个翻译单位应该有自己的 file_counter 副本。但我得到的输出是:

From Incrementor1...
Global counter is: 6
From Incrementor2...
Global counter is: 7
From Incrementor1...
File counter is: 1
From Incrementor2...
File counter is: 2

所以,我认为两个增量器都有一个 file_counter 的副本,这是一个全局变量。但是现在我不知道如何复制给定的答案并让他们拥有自己的副本。有什么建议吗?

顺便说一下,如果我想在 extern.h 中给 file_counter 一个定义,因为它包含在两个增量器中,我会收到多个定义错误。这让我更加困惑如何让他们拥有共享副本。

由于暂定定义,您遇到了问题

引用 C11,章节 §6.9.2

A declaration of an identifier for an object that has file scope without an initializer, and without a storage-class specifier or with the storage-class specifier static, constitutes a tentative definition. If a translation unit contains one or more tentative definitions for an identifier, and the translation unit contains no external definition for that identifier, then the behavior is exactly as if the translation unit contains a file scope declaration of that identifier, with the composite type as of the end of the translation unit, with an initializer equal to 0.

尝试使用 -fno-common 标志进行编译,它应该抛出与 file_counter.

的重新定义相关的错误

引用 online manual

-fno-common

In C code, this option controls the placement of global variables defined without an initializer, known as tentative definitions in the C standard. Tentative definitions are distinct from declarations of a variable with the extern keyword, which do not allocate storage.

Unix C compilers have traditionally allocated storage for uninitialized global variables in a common block. This allows the linker to resolve all tentative definitions of the same variable in different compilation units to the same object, or to a non-tentative definition. This is the behavior specified by -fcommon, and is the default for GCC on most targets. On the other hand, this behavior is not required by ISO C, and on some targets may carry a speed or code size penalty on variable references.

The -fno-common option specifies that the compiler should instead place uninitialized global variables in the data section of the object file. This inhibits the merging of tentative definitions by the linker so you get a multiple-definition error if the same variable is defined in more than one compilation unit. Compiling with -fno-common is useful on targets for which it provides better performance, or if you wish to verify that the program will work on other systems that always treat uninitialized variable definitions this way.

So by the answer linked above, I think global_counter should be shared by all translation units while each should have their own copy of file_counter

这是不正确的。 global_counterfile_counter 都是全局变量。这是因为您在任何函数之外声明了 file_counter

如果你在函数incrementor1_localincrementor2_local中单独声明file_counter并从extern.h中删除它,那么它将是一个局部变量并且会有单独的副本在每个函数中。

int file_counter 是一个暂定定义,是一个全局变量(具有外部链接),因为它是在任何函数之外定义的。在 C 源文件中而不是在头文件中定义变量是一个好习惯。

如果您希望每个翻译单元都有自己的 file_counter 副本,一种方法是将其标记为 static 并在 incrementor1.c 和 [=14= 中定义它].在这种情况下,变量的可见性将仅限于定义它的文件。

包含 extern.h 的每个编译单元都在全局范围内声明了一个名为 file_counter 的变量。这意味着每个人都声明变量和一些存储空间,但理论上变量在 link 时间对其他编译单元可见。

当 linker 出现时,它会看到这些全局变量并将它们分配到一段存储,并且在您的情况下,它将它们合并到一个实例中,我不确定这是不是是否由 C 标准强制要求,我懒得检查,但这可能是未定义行为的一个例子。

你需要的是使变量 file-counter 只在声明它的编译单元(包含 .h.c 文件)中可见,为此,您使用 static 修饰符

static int file_counter;

作为规则,它也应该从header 中删除并放置在将要使用它的.c 文件中。对于演示 externstatic 之间差异的玩具示例来说,这是可以的,但在几乎每个真实场景中,您不希望在每个包含header.