m的静态声明遵循非静态声明

10

我正在尝试一个小例子来了解静态外部变量及其用途。静态变量是局部作用域的,而外部变量是全局作用域的。

static5.c

#include<stdio.h>
#include "static5.h"
static int m = 25;
int main(){
 func(10);
 return 0;
}

static5.h

#include<stdio.h>
int func(val){
 extern int m;
 m = m + val;
 printf("\n value is : %d \n",m);
}

gcc static5.c static5.h

o/p :

static5.c:3: error: static declaration of m follows non-static declaration
static5.h:3: note: previous declaration of m was here

已编辑

正确的程序:

a.c:
#include<stdio.h>
#include "a1_1.h"
int main(){
 func(20);
 return 0;
}

a1.h:
static int i = 20;

a1_1.h:
#include "a1.h"
int func(val){
 extern int i;
 i = i + val;
 printf("\n i : %d \n",i);
}

这个工作得很好。但是它被编译成了一个单一的编译单元。因此可以访问静态变量。在编译单元之间,我们不能使用extern变量来使用静态变量。


extern int m; 不应该放在函数体内吗? - Kninnug
1
m在static5.c中被声明为静态变量,因此其作用域仅限于该文件内,所以会出现错误。 - Santhosh Pai
那么问题是什么呢?m是static类型的,所以它只能在static5.c文件中访问,即使使用extern关键字声明也无法在其他地方访问。 - greydet
2
在头文件中定义任何函数都是不寻常且潜在危险的。 - aschepler
4个回答

20

static的逻辑非常简单。如果一个变量是static,那么它是一个全局变量,但是其作用域仅限于定义它的地方(即仅在该处可见)。例如:

  • 在函数外:全局变量,但仅在文件内可见(实际上是编译单元)
  • 在函数内部:全局变量,但仅在函数内可见
  • (C++)在类内部:全局变量,但仅对该类可见

现在让我们看看C11标准关于staticextern的规定(强调是我的):

6.2.2.3

如果对象或函数的文件作用域标识符的声明包含存储类别说明符static,则该标识符具有内部链接。

6.2.2.4

如果在可以看到先前声明的标识符的范围内使用存储类别为extern的标识符声明,并且先前的声明指定了内部或外部链接,则稍后声明处的标识符的链接与先前声明处指定的链接相同。 如果没有看到先前的声明,或者先前的声明未指定链接,则该标识符具有外部链接。

6.2.2.7

如果在一个翻译单元中,同一标识符既具有内部链接又具有外部链接,则行为是未定义的。

因此,标准首先指出:

static int m;
extern int m;

然后第二个声明(使用extern)将涉及第一个声明,最终m仍将是static

但是,在任何其他情况下,如果有具有内部和外部链接的声明,则行为未定义。这实际上只留下了一种选择:

extern int m;
static int m;

即在static声明之前进行extern声明。在这种情况下,gcc会友善地给出错误提示,因为这是未定义行为


你好,感谢您详细的解释。但我有点困惑为什么会同时使用 staticextern - 它似乎在告诉我“我希望变量 a 仅与该文件相关联 (static),并且我会将其暴露给所有其他文件 (extern)”。这听起来似乎矛盾。我的理解正确吗?如果不是,那么什么时候需要同时使用 staticextern 呢?提前致谢。 - Unheilig
@unheilig,对于你的个人项目而言是正确的,但在一般情况下也有其价值。以static修饰符在extern之前的情况为例,想象一个带有常规头文件和外部函数声明的库。通过在包含头文件之前将其中某些函数声明为static,你可以覆盖其中一些函数。例如,这种方法可以实现一种编译时启用的valgrind功能。 - Shahbaz
另一种方式可能存在问题,因为在extern声明之后,编译器会生成与该声明链接的代码,但是后来你说它应该是static,所以编译器必须返回并修复先前的代码。由于C编译器是单通道的,这最多是令人烦恼的。 - Shahbaz

17

请记住 Eli Bendersky 的话:

  • 函数内的静态变量在调用之间保持其值。
  • 静态全局变量或函数只能在声明它的文件中“看到”

在您的代码中,static int m = 25; 表示 m 的范围仅限于该文件,即它仅在 static5.c 中可见。

如果您想在 static5.c 之外使用 m,请确保从变量的声明中删除关键字 static

有关更详细的解释和示例,请参见Eli Bendersky 的这个答案

编辑(根据 Klas 的建议) 实际范围是编译单元,而不是源文件。编译单元是经过预处理步骤后文件的外观


5
作用域是编译单元,而不是源文件。编译单元是在预处理步骤后文件的外观,然后 func 在同一文件中。 - Klas Lindbäck
@KlasLindbäck 感谢您的评论!我将会更新我的答案。 - NlightNFotis
@NlightNFortis:只有一个编译单元。我使用gcc static5.c和static5.h进行编译,代码被编译成单个编译单元。但是仍然出现错误。 - Angus
@Angus 是的,预处理器会将 static5.c 中的 include "static5.h" 更改为该文件的源代码,因此当实际编译时,编译器发现了 m 的一个非静态声明(在 static5.h 函数内部)在找到静态声明之前(在 static5.c 文件中)。 - NlightNFotis
一个函数内的静态变量实际上存储在哪里?它是否像普通全局变量一样存储在.data中? - ljk321

3
问题正如错误信息所述。变量m被声明为普通int类型,但后来被定义成了static int类型。
extern告诉编译器/链接器在全局变量表中查找该变量。
(函数外的)static告诉编译器将该变量排除在全局变量表之外。
你看到这个冲突了吗?
要解决这个问题,可以从定义中删除static关键字或将定义移到引用static5.h之前。
需要注意的是,按照您设计的文件方式不符合最佳实践。包含文件通常不包含函数。

我一开始也是这么想的,但标准对此更加灵活。如果static声明在extern声明之前,那就完全没问题。请参考我的答案以获取参考资料。唯一的问题是如果extern声明先出现。 - Shahbaz
@Shahbaz 感谢澄清。我不确定 extern 声明是错误还是多余的。 - Klas Lindbäck

2

在声明m时去掉关键字static,错误将被删除,您将能够得到答案50。静态关键字使作用域限制在文件内部。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接