通过给出here的答案,我试图构建一个工作示例并尝试理解extern链接的行为。
以下是我的例子:
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();
}
由于我将.o文件链接到我的main,我只是声明函数,因为默认情况下它们是extern,所以它们是由我理解的链接器链接的。最后,如果您想自己尝试一下,这是一个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
因此,通过上面链接的答案,我认为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
所以,我认为两个incrementor都有一个file_counter副本,那是一个全局变量。但现在我不知道如何复制给出的答案并让他们拥有自己的副本。有什么建议吗?
顺便说一句,如果我想在extern.h中给file_counter定义,因为它包含在两个增量器中,我得到多个定义错误。这让我更加困惑于如何让他们拥有共享副本。
答案 0 :(得分:3)
由于暂定定义,您遇到了问题。
引用C11
,章节§6.9.2
具有没有初始化程序的文件范围的对象的标识符声明,以及 没有存储类说明符或存储类说明符
static
,构成一个 暂定的定义。如果翻译单元包含一个或多个临时定义 然后,标识符和转换单元不包含该标识符的外部定义 行为就像翻译单元包含该文件范围声明一样 标识符,具有复制类型,如翻译单元的末尾,带有初始化程序 等于0。
尝试使用-fno-common
标志进行编译,它应该抛出与file_counter
的重新定义相关的错误。
-fno-common
在C代码中,此选项控制在没有初始化程序的情况下定义的全局变量的放置,在C标准中称为暂定定义。暂定定义与使用extern关键字的变量声明不同,后者不分配存储空间。
Unix C编译器传统上为公共块中未初始化的全局变量分配存储空间。这允许链接器将不同编译单元中相同变量的所有暂定定义解析为同一对象,或解析为非暂定定义。这是-fcommon指定的行为,是大多数目标上GCC的默认行为。另一方面,ISO C不要求这种行为,并且在某些目标上可能会对变量引用带来速度或代码大小的惩罚。
-fno-common选项指定编译器应该将未初始化的全局变量放在目标文件的数据部分中。这会禁止链接器合并临时定义,因此如果在多个编译单元中定义了相同的变量,则会出现多重定义错误。使用-fno-common进行编译对于提供更好性能的目标非常有用,或者如果您希望验证程序是否可以在其他始终以这种方式处理未初始化变量定义的系统上运行。
答案 1 :(得分:2)
int file_counter
是一个暂定定义,是一个全局变量(带有外部链接),因为它是在任何函数之外定义的。在C源文件中使用变量定义是一种很好的做法,而不是在头文件中。
如果您希望每个翻译单元都拥有自己的file_counter
副本,则可以选择将其标记为static
,并在incrementor1.c
和incrementor2.c
中对其进行定义。在这种情况下,变量的可见性将仅限于定义它的文件。
答案 2 :(得分:1)
包含extern.h
的每个编译单元在全局范围内声明一个名为file_counter
的变量。这意味着每个都为它声明变量和一些存储空间,但该变量在链接时理论上可见于其他编译单元。
当链接器出现时,它会看到这些全局变量并将它们分配给一个存储段,在你的情况下,它将它们合并到一个实例中,我不确定这是否是C标准强制要求的,我懒得检查,但它可能是未定义行为的一个例子。
您需要的是使变量file-counter
仅在声明它的编译单元(包含.c
的{{1}}文件)中可见,为此,您使用.h
修饰符
static
通常,它也应该从标题中删除并放在要使用它的static int file_counter;
文件中。您的玩具示例可以显示.c
和extern
之间的差异,但在几乎所有实际情况中,您都不希望在每个static
文件中包含新的静态变量报头中。
答案 3 :(得分:0)
所以通过上面的答案,我认为global_counter应该由所有翻译单元共享,而每个翻译单元应该有自己的file_counter副本
这是不正确的。 global_counter
和file_counter
都是全局变量。这是因为您在任何函数之外声明file_counter
。
如果您在函数file_counter
和incrementor1_local
内单独声明incrementor2_local
并将其从extern.h
中删除,那么它将是一个局部变量,并且每个变量都有单独的副本功能