C语言中的静态变量

6

你好,我正在学习C语言考试,遇到了一个问题,但是我无法找出答案。

一个程序员编写了一个统计用户数量的程序(Count.h, Count.c):

/******** FILE: Counter.h ***********/
static int counter = 0;
int getUsersNum ();
/******** END OF FILE: Counter.h ****/

/******** FILE: Counter.c ***********/
#include "Counter.h"
int getUsersNum ()
{
    return counter;
}
/******** END OF FILE: Counter.c ****/

还需要一个测试人员来测试它:

/******** FILE: CounterMain.c ***********/
#include "Counter.h"
#include <stdio.h>
int main ()
{
    int i;
    for (i=0;i<5;++i)
    {
        ++counter;
        printf ("Users num:  %d\n", getUsersNum());
    }
    return 0;
}
/******** END OF FILE: CounterMain.c ****/

令人惊讶的是,输出结果为:

Users num: 0
Users num: 0
Users num: 0
Users num: 0
Users num: 0

我不明白为什么使用静态变量后计数器没有增加,他们是如何得到这样的输入的?

谢谢大家!

7个回答

14
在C语言中,静态变量的作用域是定义它的源文件。由于您将此头文件加载到两个不同的.c文件中,每个文件都会得到一个唯一的变量。在一个文件中递增"counter"不会影响另一个文件中的"static"变量。如需详细了解,请参见此描述。为了使变量在多个文件中可见且共享,需要将其声明为extern。否则,静态全局变量:在源文件的顶层(任何函数定义之外)声明为静态的变量在整个文件("文件作用域")中可见。这就是这里的情况。

4
“源文件”这个词有些令人困惑,因为头文件也是源文件,而且变量只在一个源文件中声明。使用“翻译单元”可能更准确。 - CB Bailey
2
在C语言中,静态变量的作用域是定义它的翻译单元。 - Prasoon Saurav
@Charles:太快了!我正在发布完全相同的东西。:D - Prasoon Saurav
1
@Prasoon和@Charles:那是直接引用,不是我的文本。这就是为什么我在我的答案中特别提到了“.c文件”。 - Reed Copsey
@Prasoon:虽然“翻译单元”是正确的术语,但对于原帖的发布者来说可能不太明显... - Reed Copsey
可能不是很明显,也许需要解释,但我认为使用翻译单元的概念来解释行为是否使用正确的技术术语是必要的。在我的看法中,松散地使用“源文件”这个术语可能会更加令人困惑。 - CB Bailey

13

可以将其理解为“.h文件不存在”。实际上,.h文件被包含在.c文件中,并且只有.c文件被编译(并链接(混合)在一起)。

在您的情况下,您有2个带有.c扩展名的文件。

static int counter = 0;

每个计数器都是特定于其所在的.c文件的。 CounterMain.c中的计数器是与Counter.c中的计数器不同的变量。
您需要有一个单一的计数器定义。您可以有多个声明(通常在.h文件中)。
/* .h file */
extern int counter;

/* .c file(s) that use the counter but don't define it */
#include "file.h"

/* .c file that **defines** counter */
#include "file.h"
int counter = 0;

,还有static这个东西。不要在全局范围内使用它!


2
发表简洁明了的解释和解决方案,而不是细节精确但令人感到可怕,这是一个很好的做法。 - André Caron

3

将一个或多个翻译单元组合在一起就可以创建C程序。

翻译单元实际上是经过预处理的源文件。它包含在#include指令中指定的任何已包含的头文件和源文件,但不包括由#if或类似指令排除的任何内容。

当文件作用域的变量声明为static时,它会给变量名提供内部链接。这意味着该名称引用与其出现的翻译单元本地的对象。如果在另一个翻译单元中使用该名称,则不能引用该翻译单元中的对象,而必须引用不同的对象。

[相比之下,具有外部链接的名称无论在哪个翻译单元中使用,都引用相同的对象。]

static int counter = 0;

当您在头文件中放置这样的声明时,它意味着每个包含该头文件的翻译单元都有自己独特的对象,称为counter,该对象与任何其他翻译单元中名为counter的对象不同。
在您的情况下,从CounterMain.c生成的翻译单元中有一个counter,而从Count.c生成的翻译单元中有另一个counter。在Count.c中的counter从未被递增,但由getUserNum()返回,在CounterMain.c中的countermain中递增,但在其他任何地方都没有使用。

2
static关键字用于修饰函数或全局变量,表示该函数或变量应具有内部链接,即不应作为全局符号可见。因此,包括Counter.h在内的每个编译单元都将拥有自己的本地副本counter,不会与其他副本冲突。
在这种情况下,Counter.cCounterMain.c具有不同的变量count,导致您所描述的结果。
解决方法是将Counter.hcounter的定义更改为声明:
extern int counter;
并将定义放在Counter.c中:
int counter = 0; 之后,CounterMain和任何其他包含Counter.h的编译单元都应能够访问单个实例counter,但您可能希望进行信息隐藏,并通过Counter中的函数访问它,从而获得更清晰的接口

1
“static” 在函数和变量中有什么不同? - CB Bailey
@Charles Bailey 当你深入研究生成的代码时,情况就有所不同了,但如果你只看C语言层面,它在语义上基本相同。 - zneak
它不适用于函数和全局变量:正如我所解释的那样,它会改变链接(我已编辑我的答案以读取“变量或函数”)。对于简单的本地变量,它意味着“静态存储和生命周期”,即普通的静态变量。 - aib
1
当应用于具有块作用域的对象时,它与应用于函数和具有文件作用域的对象时含义不同。你的第一句话含糊不清,因为你没有说明在说“有不同含义”时正在比较的上下文是什么。 - CB Bailey
啊,我现在明白你的意思了。已经修复了。 - aib
显示剩余3条评论

2

static int counter = 0;

如果一个变量使用了静态存储类别说明符进行定义,那么该变量具有内部链接。这意味着你可以在同一翻译单元中使用 counter


1

static变量只能在定义它的文件中访问。在这个例子中,Counter.cCounterMain.c都有自己的counter变量。

当执行++counter时,会更新在CounterMain.c中声明的变量。但是当调用getUsersNum()时,会返回Counter.c中的counter变量的值,该值尚未递增。

如果将getUsersNum()更改为counter,则可以看到在CounterMain.c中声明的counter变量已经递增。


1
这是因为静态变量在头文件中声明。在C语言中,静态变量只存在于它们被声明的.c文件中。由于您的.h文件被包含在两个不同的.c文件中(#include指令可以看作是一种复制粘贴操作),因此会创建两个名为counter的静态变量,一个在每个文件中。您的测试文件增加了其本地的counter变量,但由getUsersNum返回的变量来自另一个C文件,并且完全独立。
问题所尝试的是从声明它的文件之外的文件访问静态变量。您应该知道这是不可能的(直接)。要增加正确的计数器,您需要一个操作Counter.ccounter变量的函数。

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