代码之家  ›  专栏  ›  技术社区  ›  hhafez

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

  •  21
  • hhafez  · 技术社区  · 16 年前

    在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 回复  |  直到 14 年前
        1
  •  35
  •   paxdiablo    14 年前

    这种混乱通常是因为 static 关键字有两个用途。

    在文件级使用时,它控制 能见度 它的对象在编译单元之外,而不是 期间 对象(可见性和持续时间是我在教育课程中使用的外行术语,ISO标准使用不同的术语,您可能希望最终了解这些术语,但我发现它们混淆了大多数刚开始学习的学生)。

    在文件级创建的对象的持续时间已经由它们在文件级这一事实决定了。这个 静止的 然后关键字只会使链接器看不到它们。

    当在函数内部使用时,它控制 期间 不是 能见度 . 可见性已经决定了,因为它在函数内部——在函数外部看不到它。这个 静止的 关键字在这种情况下,会导致对象与文件级对象同时创建。

    请注意,在技术上,函数级静态可能不一定存在,直到函数被首次调用(并且它对于C++具有它的构造函数可能是有意义的),但是我所使用的每一个C实现都在文件级对象的同时创建它的函数级静态。

    而且,当我使用“对象”这个词时,我并不是指C++对象的意义(因为这是一个C问题)。只是因为 静止的 可以应用于文件级的变量或函数,我需要一个包含所有内容的词来描述它。

    函数级静态仍然被使用了相当长一段时间——如果不满足这一点,它们可能会在多线程程序中造成麻烦,但是,如果您知道您正在做什么(或者您没有线程化),它们是在提供封装的同时保持多个函数调用状态的最佳方法。

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

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

    在这两种情况下,您将向其客户机公开函数的内部工作,并使函数依赖于客户机的良好行为(始终是一个风险假设)。

        2
  •  9
  •   dmckee --- ex-moderator kitten    16 年前

    它们用于实现以下工具 strtok 它们会导致重新进入的问题…

    在使用这个工具之前要仔细考虑,但有时它们是合适的。

        3
  •  7
  •   jpalecek    16 年前

    例如,在C++中,它被用作一种获取单进程的方式。

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

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

    ad“函数不应该在自己的文件中”

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

    顺便说一句,正如其他人指出的,几乎所有反对全局变量的论点也适用于静态变量,因为它们 实际上是全球性的。但在很多情况下,使用全球服务是可以的,人们也可以。

        4
  •  4
  •   DougN    16 年前

    我发现它对于一次性、延迟、初始化很方便:

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

    正如其他人所说,在第一次调用时,这不是线程安全的,但有时您可以摆脱它:)

        5
  •  1
  •   Dave Markle    16 年前

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

    其他语言如C甚至不支持它。我认为以前的想法是,在OO语言出现之前,它提供了一些封装的外观(如果您可以这样称呼的话)。

        6
  •  1
  •   anon    16 年前

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

        7
  •  1
  •   gbarry    16 年前

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

        8
  •  1
  •   George    16 年前

    静态变量的一些用例:

    • 您可以将其用于计数器,并且不会污染全局命名空间。
    • 可以使用一个函数来保护变量,该函数将值作为指针并返回内部静态值。您可以控制如何分配值。(当您只想获取值时使用空值)
        9
  •  1
  •   user62572    16 年前

    我从来没有听说过这种特殊的结构,叫做“内部静态变量”,我想是一个合适的标签。

    像任何建筑一样,它必须被知识和负责任地使用。您必须知道使用构造的后果。

    它将变量保存在大多数本地范围内声明,而不必为函数创建单独的文件。它还防止全局变量声明。

    例如:

    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
    

    其中一个分支是这些函数既不可重入,也不线程安全。

        10
  •  1
  •   MSN    16 年前

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

        11
  •  1
  •   Stephen Friederichs    16 年前

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

        12
  •  1
  •   Casey Barker    15 年前

    所有静态都是持久的,并且不受同时访问的保护,就像全局访问一样,因此必须谨慎使用。然而,有时它们确实能派上用场,不一定值得放在自己的文件里。

    我在一个致命的错误日志记录函数中使用了一个,它被修补到我目标的错误中断向量上,例如,按零划分。当调用此函数时,将禁用中断,因此线程处理不是问题。但如果我 引起 在记录第一个错误的过程中出现的新错误,例如错误字符串格式化程序是否中断。那样的话,我就得采取更激烈的行动。

    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();
    }
    

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

        13
  •  0
  •   lillq    16 年前

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