C语言中的静态函数

183
在C语言中,将函数声明为static的作用是什么?

8
C++中不存在所谓的“methods”。我认为你可能混淆了Objective-C。 - Bo Persson
1
不好意思,我对Python有点困惑。在Python中,类内的函数被称为方法。 - orlp
7
可能是什么是C语言中的“静态函数”?的重复问题。 - atoMerz
7个回答

225

将函数定义为static可以将其隐藏在其他翻译单元中,这有助于提供封装

helper_file.c

int f1(int);        /* prototype */
static int f2(int); /* prototype */

int f1(int foo) {
    return f2(foo); /* ok, f2 is in the same translation unit */
                    /* (basically same .c file) as f1         */
}

int f2(int foo) {
    return 42 + foo;
}

main.c:

int f1(int); /* prototype */
int f2(int); /* prototype */

int main(void) {
    f1(10); /* ok, f1 is visible to the linker */
    f2(12); /* nope, f2 is not visible to the linker */
    return 0;
}

10
这里使用“翻译单元”一词是否正确?使用“目标文件”会更准确吗?据我所知,静态函数对链接器是隐藏的,而链接器不对翻译单元进行操作。 - Steven Eckhoff
3
我还应该说,我喜欢把它想象成对连接器隐藏的状态;这样似乎更清晰明了。 - Steven Eckhoff
2
你怎么编译这个程序?你使用 #include <helper_file.c> 吗?我认为那样会使它成为一个单一的翻译单元... - Atcold
2
@Atcold:我编写代码的方式是在命令行中包含这两个源文件,就像这样 gcc -std=c99 -pedantic -Wall -Wextra main.c helper_file.c。函数的原型在两个源文件中都存在(不需要头文件)。链接器将解析这些函数。 - pmg
2
当您不将函数声明为static时,链接器即使没有原型也可以找到它。缺少原型会防止编译器检查返回和参数类型,但不会阻止其发出调用该函数的代码。 - pmg
显示剩余6条评论

86

pmg关于封装的观点是正确的;除了隐藏函数不被其他翻译单元调用(或者更确切地说,正因为此),将函数设为static还可以在编译器进行优化时提供性能优势。

因为static函数不能从当前翻译单元之外的任何地方调用(除非代码采用其地址的指针),所以编译器控制所有进入它的调用点。

这意味着它可以使用非标准的ABI,完全内联它,或执行任何数量的其他优化,而对于具有外部链接的函数可能不可能实现这些优化。


12
除非获取函数的地址。 - caf
1
@caf 你说的函数地址被取走是什么意思?对我来说,函数/变量具有地址或在编译时分配地址的概念有点令人困惑。你能详细解释一下吗? - SayeedHussain
3
@crypticcoder:你的程序已经加载到内存中,因此函数也有一个内存位置,可以获取地址。使用函数指针,你可以调用其中任何一个函数。如果这样做,它会减少编译器可以执行的优化列表,因为代码必须保持在同一位置不变。 - user1454661
6
我的意思是一个表达式会评估函数的指针,并对其进行一些操作,而不是立即调用该函数。如果一个指向“静态”函数的指针逃离了当前的翻译单元,那么该函数可以直接从其他翻译单元中调用。 - caf
1
如果函数地址被获取,编译器是否会检测到并关闭本答案中提到的静态函数优化(例如使用非标准 ABI)?我想它必须这样做。 - sevko
1
@sevko:是的-不过这并不太难,因为至少任何获取函数地址的表达式都必须在同一个源文件中。 - caf

33

static关键字用于C语言中的编译文件(.c文件,而不是.h文件),这样该函数只存在于该文件中。

通常,在创建一个函数时,编译器会生成一些垃圾代码,以便链接器可以使用它们将函数调用链接到该函数。如果使用了static关键字,则同一文件中的其他函数可以调用此函数(因为可以直接访问,无需借助链接器),而链接器没有提供任何信息让其他文件访问该函数。


2
3Doub:使用“cruft”这个词比你认为的更精确。在这个问题的背景下,“cruft”是正确的词。 - Erik Aronesty
1
@3Doubloons 我同意它是简化的,但我认为这使得初学者更容易理解。 - Ingo Bürk

14

看了上面的帖子,我想给出一个更明确的答案:

假设我们的main.c文件长这样:

#include "header.h"

int main(void) {
    FunctionInHeader();
}

现在考虑三种情况:

案例 1: 我们的"header.h"文件如下所示:
#include static void FunctionInHeader();
void FunctionInHeader() { printf("在头文件内调用函数\n"); }
然后在Linux上执行以下命令:
gcc main.c -o main
将会成功!这是因为在"main.c"文件包含了"header.h"之后,静态函数定义就会存在于相同的"main.c"文件中(更准确地说,在相同的翻译单元)。
如果运行"./main",将会输出"在头文件内调用函数",这正是该静态函数应该打印出的内容。
案例 2:我们的头文件"header.h"如下所示:
static void FunctionInHeader();
还有另外一个文件"header.c",其内容如下:
#include #include "header.h"
void FunctionInHeader() { printf("在头文件内调用函数\n"); }
然后执行以下命令:
gcc main.c header.c -o main
将会出现错误。在这种情况下,"main.c"只包含了静态函数的声明,而定义却留在了另一个翻译单元中,并且"static"关键字阻止了代码定义函数的链接。
案例 3: 与案例2类似,只不过现在我们的头文件"header.h"变成了:
void FunctionInHeader(); // 移除了"static"关键字
然后执行和案例2相同的命令,将会成功,并且继续执行"./main"会得到预期结果。这里的"FunctionInHeader"定义位于另一个翻译单元中,但是可以链接该定义的代码。

因此,总之:

static keyword prevents the code defining a function to be linked,
when that function is defined in another translation unit than where it is called.

5

C程序员使用static属性来隐藏模块内的变量和函数声明,就像您在Java和C++中使用public和private声明一样。 C源文件扮演模块的角色。任何使用static属性声明的全局变量或函数都是私有的,只能由该模块访问。同样,任何未使用static属性声明的全局变量或函数都是公共的,可以被任何其他模块访问。在可能的情况下,使用static属性保护您的变量和函数是良好的编程实践。


5

pmg的回答非常有说服力。如果您想了解静态声明在对象级别上是如何工作的,那么以下信息可能对您很有帮助。

我重用了pmg编写的同一程序,并将其编译成.so(共享对象)文件。

以下内容是将.so文件转储为人类可读格式后的结果:

0000000000000675 f1: f1函数的地址

000000000000068c f2: f2(静态)函数的地址

注意函数地址的差异,这意味着某些事情。对于使用不同地址声明的函数,它可以非常明显地表示f2位于非常远或对象文件的不同段中。

链接器使用称为PLT(过程链接表)和GOT(全局偏移表)的东西来理解它们可以访问的符号链接到哪里。

现在暂时认为GOT和PLT神奇地绑定了所有地址,动态部分保存了链接器可见的所有这些函数的信息。

转储.so文件的动态部分后,我们得到了一堆条目,但只对f1f2函数感兴趣。

动态部分仅保存了f1函数的地址为0000000000000675,而没有保存f2的地址!

Num: Value Size Type Bind Vis Ndx Name

 9: 0000000000000675    23 FUNC    GLOBAL DEFAULT   11 f1

就是这样!从中可以清楚地看出,由于f2函数不在.so文件的动态部分中,因此链接器将无法找到它。


0

当需要限制对某些函数的访问时,我们会在定义和声明函数时使用 static 关键字。

            /* file ab.c */ 
static void function1(void) 
{ 
  puts("function1 called"); 
} 
And store the following code in another file ab1.c

/* file ab1.c  */ 
int main(void) 
{ 
 function1();  
  getchar(); 
  return 0;   
} 
/* in this code, we'll get a "Undefined reference to function1".Because function 1 is declared static in file ab.c and can't be used in ab1.c */

1
这个答案并不是很有帮助。 - fiscblog

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