函数内静态变量的用途

3

我已经写了很多年的C代码,但最近遇到了一个我从未使用过的特性:函数内的静态变量。因此,我想知道你们有哪些使用这个特性的方法,并且这样做是正确的设计决策。

例如:

int count(){
    static int n;
    n = n + 1;
    return n;
}

这是一个糟糕的设计决策。为什么呢?因为后来你可能想要减少计数,这将涉及更改函数参数、更改所有调用代码等操作...

希望这已经足够清楚了,谢谢!

4个回答

4
void first_call()
{
   static int n = 0;

   if(!n) {
     /* do stuff here only on first call */
     n++;
   }

   /* other stuff here */
}

5
这是我使用此功能的主要原因。不过需要注意的是,该代码既非可重入的也不具备线程安全性,如果有多个调用者能够发起第一个调用,则不要期望它能解决竞争问题。 - San Jacinto
请注意,pthread_once接口以线程安全的方式提供了等效的功能。 即使调用初始化函数的线程在初始化完成之前被取消,它也是安全的。(而且没有理由不在单线程程序中使用pthread_once,这些程序从未期望使用线程。) - R.. GitHub STOP HELPING ICE
@R.. 谢谢!很高兴你能为此提供一些启示。 - San Jacinto

2

我曾在测试代码中使用静态变量进行延迟状态初始化。但在生产代码中使用静态局部变量会充满风险,可能导致微妙的错误。至少在我通常处理的代码中,几乎任何一段最初只能单线程运行的代码都有最终成为并发情况下工作的危险。在并发环境中使用静态变量可能会导致难以调试的问题,因为状态改变实际上是隐藏的副作用。


不确定为什么这个回答被踩了。我认为这是目前为止最好的答案。 - Justin Spahr-Summers
@Justin,谢谢 - 在我的第一个答案中使用了“总是”这样的词语,可能过于坚定地认为这是一个不好的想法...我确实有这种感觉,但通常(总是? :))有例外。 - Mark Wilkins

0

我已经使用静态变量来控制另一个线程的执行。

例如,线程#1(主线程)首先声明和初始化一个控制变量,如下:

/* on thread #1 */
static bool run_thread = true;
// then initialize the worker thread

然后它开始执行线程#2,直到线程#1决定停止它之前,线程#2将执行一些工作:

/* thread #2 */
while (run_thread)
{
  // work until thread #1 stops me
}

这种方法在奇怪和难以诊断的情况下可能会出现严重问题,因为静态变量不能保证线程安全。您需要使用类似于 pthread_mutex 的东西来确保一致的读写。 - Justin Spahr-Summers
在我的例子中,只有第一个线程被允许改变run_thread的状态。 - karlphillip
你仍然存在可能无序的内存写入错误。除了从实现或C1x _Atomic类型获得特殊保证之外,唯一可移植的方法是使用pthread_mutex_*函数来获取必要的内存屏障。 - R.. GitHub STOP HELPING ICE
@R.. 对不起,我很无知,但是当您只写入1个字节时,内存写入不是原子操作吗?我知道多个字节可以在字节之间中断(例如C短语),但我一直认为单个字节本身是原子的。 - chacham15
@chacham15:不,一字节原子操作将是相当无用的。我怀疑许多系统甚至没有它们,尽管x86有。大多数系统都至少具有32位和指针大小的原子操作,通常还有双指针大小的操作。 - R.. GitHub STOP HELPING ICE
@R.. 让我重新表达一下,x86处理器在读/写1个字节的过程中可以被中断吗? - chacham15

0

有一个非常突出的例子,你非常需要使用static来保护关键部分,即互斥锁。以POSIX线程为例:

static pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mut);
/* critical code comes here */
pthread_mutex_unlock(&mut);

使用 auto 变量无法实现此功能。

POSIX 为互斥锁、条件变量和一次性变量提供了这样的 static 初始化器。


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