C中的内部静态变量,你会使用它们吗?

时间:2009-02-10 23:21:08

标签: c static

在C中,您可以拥有可在文件中的每个位置查看的外部静态变量,而内部静态变量仅在函数中可见但是是持久的

例如:

#include <stdio.h>

void foo_bar( void )
{
        static counter = 0;
        printf("counter is %d\n", counter);
        counter++;
}
int main( void )
{
        foo_bar();
        foo_bar();
        foo_bar();
 return 0;
}

输出将是

counter is 0
counter is 1
counter is 2

我的问题是你为什么要使用内部静态变量?如果你不希望你的静态变量在文件的其余部分可见,那么该函数真的不应该在它自己的文件中吗?

13 个答案:

答案 0 :(得分:36)

这种混淆通常是因为static关键字有两个目的。

在文件级别使用时,它控制编译单元外部对象的可见性,而不是对象的持续时间(可见性和持续时间是我使用的外行人的术语在教育课程中,ISO标准使用了您最终可能想要学习的不同术语,但我发现它们使大多数初学者感到困惑。

在文件级别创建的对象已经根据它们处于文件级别的事实来确定其持续时间。然后static关键字使它们对链接器不可见。

在内部功能中使用时,它控制持续时间,而不是可见性。可见性已经确定,因为它在函数内部 - 在函数外部看不到它。在这种情况下,static关键字会导致与文件级对象同时创建对象。

请注意,从技术上讲,函数级静态可能不一定存在,直到首次调用该函数(这对于C ++及其构造函数可能有意义),但我曾经使用的每个C实现都创建了它的函数级静态与文件级对象同时使用。

另外,虽然我使用的是“对象”这个词,但我并不是指C ++对象的意思(因为这是一个C问题)。这只是因为static可以应用于文件级别的变量或函数,我需要一个无所不包的词来描述它。

功能级静态仍然使用相当多 - 如果不能满足它们,它们可能会在多线程程序中造成麻烦但是,只要你知道自己在做什么(或者你没有线程),它们就是保持多个函数调用状态的最佳方法,同时仍然提供封装。

即使使用线程,也可以在函数中执行一些技巧(例如在函数中分配线程特定数据),以使其可行,而不会不必要地暴露函数内部。

我能想到的唯一其他选择是全局变量,每次都将“状态变量”传递给函数。

在这两种情况下,您将函数的内部工作方式暴露给客户端,并使函数依赖于客户端的良好行为(总是冒险的假设)。

答案 1 :(得分:9)

它们用于实现像strtok这样的工具,它们会导致重入问题......

在使用此工具愚弄之前要仔细考虑,但有时候它们是合适的。

答案 2 :(得分:7)

例如,在C ++中,它被用作获取单身人士的一种方式

SingletonObject& getInstance()
{
  static SingletonObject o;
  return o;
}

用于解决初始化顺序问题(尽管它不是线程安全的)。

广告“该功能不应该在自己的文件中”

当然不是,那是胡说八道。编程语言的许多重点是促进隔离,因此重用代码(局部变量,过程,结构等都这样做),这只是另一种方法。

BTW,正如其他人所指出的,几乎所有针对全局变量的参数也适用于静态变量,因为它们实际上是全局变量。但是有很多情况下可以使用全局变量,人们可以这样做。

答案 3 :(得分:4)

我觉得一次性,延迟,初始化很方便:

int GetMagic()
{
   static int magicV= -1;

   if(-1 == magicV)
   {
      //do expensive, one-time initialization
      magicV = {something here}
   }
   return magicV;
}

正如其他人所说的那样,这在第一次调用时并不是线程安全的,但有时你可以侥幸逃脱:)

答案 4 :(得分:1)

我认为人们通常远离内部静态变量。我知道strtok()使用了一个或类似的东西,因为这可能是C库中最讨厌的函数。

其他语言如C#甚至不支持它。我认为过去的想法是在OO语言之前提供一些相似的封装(如果你可以称之为)。

答案 5 :(得分:1)

在C中可能不是非常有用,但它们在C ++中用于保证命名空间范围静态的初始化。在C和C ++中,在多线程应用程序中使用它们存在问题。

答案 6 :(得分:1)

我不希望存在静态变量来强迫我将函数放入自己的文件中。如果我有许多类似的函数,每个都有自己的静态计数器,我想把它放在一个文件中怎么办?我们必须做出足够的决定来放置东西,而不需要再有一个约束。

答案 7 :(得分:1)

静态变量的一些用例:

  • 您可以将它用于计数器,并且不会污染全局命名空间。
  • 您可以使用将值作为指针获取并返回内部静态的函数来保护变量。您可以控制如何分配值。 (当你想获得值时使用NULL)

答案 8 :(得分:1)

我从未听说过这个被称为“内部静态变量”的特定结构。我想是一个合适的标签。

与任何构造一样,它必须以知识和负责任的方式使用。你必须知道使用这个结构的后果。

它保持在最本地范围内声明的变量,而不必为该函数创建单独的文件。它还会阻止全局变量声明。

例如 -

char *GetTempFileName()
{
  static int i;
  char *fileName = new char[1024];
  memset(fileName, 0x00, sizeof(char) * 1024);
  sprintf(fileName, "Temp%.05d.tmp\n", ++i);
  return fileName;
}

VB.NET支持相同的构造。

Public Function GetTempFileName() As String
  Static i As Integer = 0
  i += 1
  Return String.Format("Temp{0}", i.ToString("00000"))
End Function

对此的一个分支是这些函数不是可重入的,也不是线程安全的。

答案 9 :(得分:1)

不再。我已经在多线程域中看到或听到了函数局部静态变量的结果,并且它并不漂亮。

答案 10 :(得分:1)

所有静态都是持久的,不受同步访问的保护,就像全局变量一样,因此必须谨慎谨慎地使用。然而,它们肯定会派上用场,而且它们不一定值得在自己的文件中。

我在一个致命的错误记录功能中使用了一个,它被修补到我的目标的错误中断向量,例如。 DIV被零。调用此函数时,将禁用中断,因此线程不是问题。但是,如果我在记录第一个错误的过程中引起一个新错误,那么仍然会发生重新入侵,就像错误字符串格式化程序一样。在那种情况下,我必须采取更激烈的行动。

void errorLog(...)
{
    static int reentrant = 0;
    if(reentrant)
    {
        // We somehow caused an error while logging a previous error.
        // Bail out immediately!
        hardwareReset();
    }

    // Leave ourselves a breadcrumb so we know we're already logging.
    reentrant = 1;

    // Format the error and put it in the log.
    ....

    // Error successfully logged, time to reset.
    hardwareReset();
}

这种方法正在检查一个非常不可能发生的事件,它只是安全的,因为中断被禁用。但是,在嵌入式目标上,规则是“永不挂起”。这种方法保证(在合理范围内)硬件最终会以某种方式重置。

答案 11 :(得分:1)

在编写微控制器的代码时,我会使用本地静态变量来保存特定函数的子状态值。例如,如果我有一个每次运行main()时调用的I2C处理程序,那么它将拥有自己的内部状态保存在静态局部变量中。然后每次调用它时都会检查它处于什么状态并相应地处理I / O(将位推到输出引脚上,拉一条线等)。

答案 12 :(得分:0)

一个简单的用途是函数可以知道它被调用了多少次。