在C语言中使用静态函数和变量的原因

32

我对在 C 中使用 static 关键字作为文件内变量的作用域限制很感兴趣。

我认为构建 C 程序的标准方式是:

  • 有一些定义函数和变量的 c 文件,可能使用 static 进行作用域限定。
  • 有一些声明与之相应的 c 文件的函数和可能的变量的 h 文件,供其他 c 文件使用。私有函数和变量不会公开在 h 文件中。
  • 每个 c 文件被单独编译为一个 o 文件。
  • 所有的 o 文件被链接在一起形成一个应用程序文件。

如果变量没有在 h 文件中公开,我认为有两个原因可以声明一个全局变量为 static

  • 一个是为了可读性。通知未来的读者(包括我自己),变量没有在任何其他文件中访问。
  • 第二个是为了防止另一个 c 文件将变量重新声明为 extern。我想链接器不喜欢一个变量既是 extern 又是 static。(我不喜欢一个文件重新声明别人拥有的变量作为 extern,这种做法是否合适?)

还有其他原因吗?

static 函数也是如此。如果原型没有在 h 文件中公开,其他文件可能根本不会使用该函数,那么为什么要定义它为 static 呢?我看到同样的两个原因,但没有更多的原因。


六年后,我现在知道static并不限制作用域,它提供了内部链接。如果你将翻译单元视为作用域,那么它是相似的,但不是正确的术语。 - Gauthier
6个回答

34

谈到向其他读者传达信息时,将编译器本身视为读者。如果变量被声明为 static,那么这可能会影响优化程度。

将一个 static 变量重新定义为 extern 是不可能的,但编译器(像往常一样)会给你足够的自由去把自己绞死。

如果我在一个文件中写下 static int foo;,而在另一个文件中写下 int foo;,尽管名称和类型相同,它们被认为是不同的变量——编译器不会抱怨,但你在以后尝试阅读和/或调试代码时可能会感到非常困惑。(如果在第二种情况下我写 extern int foo;,除非我在其他地方声明了一个非静态的 int foo;,否则链接将失败。)

全局变量很少出现在头文件中,但当它们出现时,应该声明为 extern。如果没有这样做,取决于编译器,你可能会冒着每个包含该头文件的源文件都声明其自己的变量的风险:最好的情况是会导致链接失败(符号被多次定义),最糟糕的情况是会有多个令人困惑的变量重名情况。


1
有趣。我明白这个“头文件中的外部全局变量”仍然需要在c文件中定义,因为头文件中的extern只是一个声明。 - Gauthier

10

如果在文件级别上声明一个static变量(函数内的static有不同的含义),则会禁止其他单元访问它,例如,如果你尝试在另一个单元内使用该变量(用extern声明),链接器将无法找到该符号。


这正是我提到的两个原因之一:“第二个原因是防止另一个c文件将看似作用域限定的全局变量重新声明为'extern'。” 我对这种做法的质量感到疑惑? - Gauthier
2
在我看来,“static” 在三种常见用法(文件变量、函数变量、函数)中具有相同的含义,即:“它具有有限的作用域,并且在程序执行期间具有恒定的地址”。 - Gauthier
1
嗯,在这个意义上,含义是相同的,但目的是不同的 - 文件级别的 static 控制可见性,函数级别的 static 控制持久性。 - qrdl
@qrdl:但是你的帖子仍然与我提到的第二个原因相同:“防止另一个c文件将变量重新声明为extern。”。我正在寻找其他原因(如果没有表达清楚,对不起)。 - Gauthier
@Gauthier 我的意思是静态函数 - 你不必担心其他人也将他们的函数命名为 sort,只要你想要的编译单元中的函数是静态的。但是,如果你有两个非静态函数具有相同的名称,通常它们不会链接,无论你是否在 .h 文件中声明它(如果你的链接器仍然接受它,你将获得未定义的行为)。 - nos
显示剩余5条评论

9
当您声明一个静态函数时,对函数的调用是“近调用”,理论上它比“远调用”执行得更好。您可以通过谷歌搜索获取更多信息。这是我在简单的谷歌搜索中找到的链接

同样有趣的是,尽管依赖于平台(我的目标只有一个CALL指令到绝对地址,没有far/near)。 - Gauthier
gcc可以在许多平台上为静态函数切换调用约定(以获得更快的速度)。 - nos

2
如果一个全局变量被声明为静态的,编译器有时可以比没有声明为静态的更好地进行优化。因为编译器知道该变量不能从其他源文件访问,它可以更好地推断出您的代码在做什么(例如,“这个函数不修改这个变量”),这有时会导致它生成更快的代码。很少有编译器/链接器可以在不同的翻译单元之间进行这些类型的优化。

1
如果你在a.c文件中声明一个名为foo的变量,而不将其设置为静态变量,在b.c文件中再声明一个名为foo的变量,同样也不将其设置为静态变量,那么它们都会自动成为外部变量。这意味着如果你初始化这两个变量,链接器可能会抱怨,并且如果它没有抱怨,则会分配相同的内存位置。预计调试代码时会很有趣。
如果你在a.c文件中编写一个名为foo()的函数,而不将其设置为静态函数,在b.c文件中再编写一个名为foo()的函数,链接器可能会抱怨,但如果它没有抱怨,所有对foo()的调用都将调用同一个函数。预计调试代码时会很有趣。

-1

我最喜欢使用静态的方式是能够存储方法,我不必注入或创建对象来使用。在我看来,私有静态方法总是有用的,而公共静态方法则需要花费更多时间思考你正在做什么,以避免像crazyscot定义的那样,让自己陷入困境并意外地绞死自己!

我喜欢为大多数项目保留一个助手类文件夹,其中主要包含静态方法,以便快速高效地完成工作,无需对象!


2
问题涉及C语言,而不是C++。在C语言中,没有类、方法和私有成员。此外,在C++中,静态函数和静态方法是有区别的。 - el.pescado - нет войне

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