尝试演示在 C 中需要 extern for variables 的情况

Trying to demonstrate a case where extern for variables is necessary in C

我正在尝试了解 extern。根据最热门的答案之一 How to correctly use the extern keyword in C

这是为了解决 header 文件的多次包含导致同一变量的多个副本并因此导致链接错误的问题。

所以我尝试创建以下文件:

count.h

int count;

count.c

int count = 0;

add.h

int sum(int x, int y);

add.c

#include "count.h"
int sum(int x, int y){
count = count + 1;
return x+y;}

sub.h

int sub(int x, int y);

sub.c

#include "count.h"
int sub(int x, int y){
count = count + 1;
return x - y;
}

main.c

#include "count.h"
#include "add.h"
#include "sub.h"
#include <stdio.h>

int main(){
  printf("%d\n", count);
  printf("%d\n", sub(100,1));
  printf("%d\n", count);
  printf("%d\n", add(100,1));
  printf("%d\n", count);
}

编译并运行良好,输出:

0
99
1
101
2

我在原始 count.h 文件中使用或不使用 extern 得到相同的输出。那么我在答案中遗漏了什么?

现在,我认为答案是 "I'm just declaring multiple copies of count",因为没有 header 守卫,这没关系,因为多个声明是可以的,而多个定义则不是。 但如果是这样的话,我希望编译以下内容,但它不会,因为我是 "redefining count."

int main(){
   int count;
   int count; 
   int count = 0;
}

根据this answer,int count确实算作一个定义。 In C, is it valid to declare a variable multiple times?

第二种情况的不同之处在于,您在函数 内的同一作用域 中多次声明了count。你这样做了吗:

int count;
int count; 
int count = 0;

int main(){
    return 0;
}

你会没事的。这样做的原因是因为没有初始化器的声明是 暂定定义 .

摘自 C standard

的第 6.9.2 节

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.

...

4 EXAMPLE 1

int i1 = 1;         //    definition, external linkage
static int i2 = 2;  //    definition, internal linkage
extern int i3 = 3;  //    definition, external linkage
int i4;             //    tentative definition, external linkage
static int i5;      //    tentative definition, internal linkage
int i1;             //    valid tentative definition, refers to pre vious
int i2;             //    6.2.2 renders undefined, linkage disagreement
int i3;             //    valid tentative definition, refers to pre vious
int i4;             //    valid tentative definition, refers to pre vious
int i5;             //    6.2.2 renders undefined, linkage disagreement
extern int i1;      //    refers to previous, whose linkage is external
extern int i2;      //    refers to pre vious, whose linkage is internal
extern int i3;      //    refers to previous, whose linkage is external
extern int i4;      //    refers to previous, whose linkage is external
extern int i5;      //    refers to previous, whose linkage is internal

你的

int count;

count.h中实际上是一个定义,而不是单纯的声明。这是一个 暂定 定义,但作为每个暂定定义,它在包含上述内容的每个翻译单元末尾都产生了 int count 的正常完整定义。

因此,通过将 count.h 包含到多个翻译单元中,您在程序中产生了外部对象 int count 的多个定义,这在标准 C 中是正式非法的。它 "compiles and runs fine" 仅因为许多现代 C 编译器都实现了编译器扩展。

根据 Rationale for International Standard — Programming Languages — C(参见 6.2.2 标识符的链接 部分),这在由过去的 Unix 编译器(所谓的 "Relaxed Ref/Def" 模型)。然而,更正式的 K&R C 拒绝了 Unix 模型并将其替换为所谓的 "Strict Ref/Def" 模型,该模型已经只允许一个定义。后来,标准化委员会决定混合使用 "Strict Ref/Def" 模型和 "Initialization" 模型,这仍然只允许一个定义。然而,"Relaxed Ref/Def" 在旧 Unix 编译器中的流行是许多现代编译器继续支持此模型作为扩展的原因。

为了使您的程序符合标准 C 的要求,您必须从头文件中删除 int count 的定义,并将其替换为非定义声明

extern int count;

这就是 extern 对变量声明有用的原因。