609

正如评论中所阐明的,问题是关于普通的函数,而不是 static

我明白什么是static变量,但什么是static函数?

为什么如果我声明一个函数,比方说void print_matrix,比方说a.c(WITHOUT a.h) 并包含"a.c"- 我得到"print_matrix@@....) already defined in a.obj"了,但是如果我声明它,static void print_matrix那么它会编译?

更新.c只是为了澄清问题-正如你们中的许多人指出的那样,我知道包含是不好的。我只是暂时清理空间,main.c直到我更好地了解如何将所有这些功能分组到适当的.h文件.c中。只是一个临时的、快速的解决方案。

4

11 回答 11

796

static函数是仅对同一文件中的其他函数可见的函数(更准确地说是相同的翻译单元)。

编辑:对于那些认为问题的作者意味着“类方法”的人:由于问题被标记C,他的意思是一个普通的旧 C 函数。对于 (C++/Java/...) 类方法,static意味着可以在类本身上调用此方法,不需要该类的实例。

于 2009-02-17T18:27:31.853 回答
219

C 中的静态函数和 C++ 中的静态成员函数之间存在很大差异。在 C 语言中,静态函数在其编译单元之外是不可见的,这是编译到的目标文件。换句话说,将函数设为静态会限制其范围。您可以将静态函数视为其 *.c 文件的“私有”函数(尽管这并不完全正确)。

在 C++ 中,“静态”也可以应用于类的成员函数和数据成员。静态数据成员也称为“类变量”,而非静态数据成员是“实例变量”。这是 Smalltalk 术语。这意味着只有一个静态数据成员的副本由类的所有对象共享,而每个对象都有自己的非静态数据成员副本。所以静态数据成员本质上是一个全局变量,即类的成员。

非静态成员函数可以访问类的所有数据成员:静态和非静态。静态成员函数只能对静态数据成员进行操作。

考虑这一点的一种方法是,在 C++ 中,静态数据成员和静态成员函数不属于任何对象,而是属于整个类。

于 2009-02-17T18:44:01.157 回答
70

最小可运行多文件范围示例

在这里,我说明了如何static影响跨多​​个文件的函数定义范围。

交流

#include <stdio.h>

/* Undefined behavior: already defined in main.
 * Binutils 2.24 gives an error and refuses to link.
 * https://stackoverflow.com/questions/27667277/why-does-borland-compile-with-multiple-definitions-of-same-object-in-different-c
 */
/*void f() { puts("a f"); }*/

/* OK: only declared, not defined. Will use the one in main. */
void f(void);

/* OK: only visible to this file. */
static void sf() { puts("a sf"); }

void a() {
    f();
    sf();
}

主程序

#include <stdio.h>

void a(void);        

void f() { puts("main f"); }

static void sf() { puts("main sf"); }

void m() {
    f();
    sf();
}

int main() {
    m();
    a();
    return 0;
}

GitHub 上游.

编译并运行:

gcc -c a.c -o a.o
gcc -c main.c -o main.o
gcc -o main main.o a.o
./main

输出:

main f
main sf
main f
a sf

解释

  • 有两个独立的函数sf,每个文件一个
  • 有一个共享功能f

像往常一样,范围越小越好,所以static如果可以的话,总是声明函数。

在 C 编程中,文件经常被用来表示“类”,static函数表示类的“私有”方法。

一个常见的 C 模式是传递一个this结构体作为第一个“方法”参数,这基本上是 C++ 在幕后所做的。

关于它的标准是什么

C99 N1256 草案6.7.1“存储类说明符”说这static是一个“存储类说明符”。

6.2.2/3“标识符的链接”说static暗示internal linkage

如果对象或函数的文件范围标识符的声明包含存储类说明符 static,则该标识符具有内部链接。

并且 6.2.2/2 表示其internal linkage行为类似于我们的示例:

在构成整个程序的一组翻译单元和库中,具有外部链接的特定标识符的每个声明都表示相同的对象或函数。在一个翻译单元中,具有内部链接的标识符的每个声明都表示相同的对象或函数。

其中“翻译单元”是预处理后的源文件。

GCC 如何为 ELF (Linux) 实现它?

STB_LOCAL绑定。

如果我们编译:

int f() { return 0; }
static int sf() { return 0; }

并使用以下命令反汇编符号表:

readelf -s main.o

输出包含:

Num:    Value          Size Type    Bind   Vis      Ndx Name
  5: 000000000000000b    11 FUNC    LOCAL  DEFAULT    1 sf
  9: 0000000000000000    11 FUNC    GLOBAL DEFAULT    1 f

所以绑定是它们之间唯一的显着区别。Value只是它们在该.bss部分中的偏移量,因此我们希望它会有所不同。

STB_LOCAL记录在http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html的 ELF 规范中:

STB_LOCAL 局部符号在包含其定义的目标文件之外不可见。同名的本地符号可以存在于多个文件中,互不干扰

这使它成为代表的完美选择static

没有 static 的函数是STB_GLOBAL,并且规范说:

当链接编辑器组合几个可重定位的目标文件时,它不允许同名的 STB_GLOBAL 符号的多个定义。

这与多个非静态定义上的链接错误一致。

如果我们用 来加速优化-O3,该sf符号将从符号表中完全删除:无论如何它不能从外部使用。TODO 为什么在没有优化的情况下将静态函数保留在符号表上?它们可以用于任何事情吗?

也可以看看

C++ 匿名命名空间

在 C++ 中,您可能希望使用匿名命名空间而不是静态,这可以达到类似的效果,但进一步隐藏了类型定义:未命名/匿名命名空间与静态函数

于 2015-05-19T07:56:24.103 回答
21

以下是关于普通 C 函数的 - 在 C++ 类中,修饰符 'static' 有另一个含义。

如果您只有一个文件,则此修饰符绝对没有区别。不同之处在于具有多个文件的较大项目:

在 C 中,每个“模块”(sample.c 和 sample.h 的组合)都是独立编译的,然后每个编译的目标文件(sample.o)都通过链接器链接到一个可执行文件。

假设您有几个文件包含在主文件中,其中两个有一个仅为方便起见在内部使用的函数add(int a, b)- 编译器很容易为这两个模块创建目标文件,但链接器会抛出错误,因为它找到了两个同名的函数,但它不知道应该使用哪一个(即使没有要链接的东西,因为它们没有在其他地方使用,而是在它自己的文件中使用)。

这就是为什么您将这个仅在内部使用的函数设为静态函数的原因。在这种情况下,编译器不会为链接器创建典型的“你可以链接这个东西”——标志,这样链接器就看不到这个函数,也不会产生错误。

于 2013-04-25T22:49:07.437 回答
16

静态函数定义会将此符号标记为内部符号。所以它对于从外部链接是不可见的,而只对同一个编译单元中的函数是可见的,通常是同一个文件。

于 2009-02-17T18:28:44.940 回答
16

第一:.cpp在另一个文件中包含一个文件通常是一个坏主意——它会导致这样的问题:-) 通常的方法是创建单独的编译单元,并为包含的文件添加一个头文件。

第二:

C++ 在这里有一些令人困惑的术语——直到在评论中指出,我才知道它。

a) static functions- 从 C 继承,以及您在此处谈论的内容。任何课外。静态函数意味着它在当前编译单元之外不可见 - 因此在您的情况下 a.obj 有一个副本,而您的其他代码有一个独立的副本。(使用多个代码副本使最终可执行文件膨胀)。

b) static member function- 什么是面向对象术语的静态方法。住在一个班级里。您使用类而不是通过对象实例调用它。

这两个不同的静态函数定义是完全不同的。小心——这里是龙。

于 2009-02-17T18:40:41.087 回答
10

"什么是 C 中的“<code>静态”函数? "

让我们从头开始。

这一切都基于一个叫做“链接”的东西:

在不同范围或同一范围内多次声明的标识符可以通过称为链接的过程来引用同一对象或函数。29)链接分为三种:外部,内部和无。

资料来源:C18,6.2.2/1


“在构成整个程序的一组翻译单元和库中,具有外部链接的特定标识符的每个声明都表示相同的对象或函数。在一个翻译单元中,具有内部链接的标识符的每个声明都表示相同的对象或函数. 每个没有链接的标识符声明都表示一个唯一的实体。

资料来源:C18,6.2.2/2


如果在没有存储类说明符的情况下定义函数,则extern默认情况下该函数具有所有链接:

“如果函数标识符的声明没有存储类说明符,则它的链接将完全确定,就好像它是使用存储类说明符extern声明的一样。”

资料来源:C18,6.2.2/5

这意味着 - 如果您的程序包含多个翻译单元/源文件(.c.cpp) - 该功能在您的程序具有的所有翻译单元/源文件中可见。

在某些情况下,这可能是一个问题。如果您想使用 fe 两个不同的函数(定义),但在两个不同的上下文(实际上是文件上下文)中使用相同的函数名称怎么办。

在 CC++ 中,static应用于文件范围内的函数(不是 C++ 中的类的静态成员函数或另一个块中的函数)的存储类限定符现在会有所帮助,并表示相应的函数仅在内部可见它是在其中定义的翻译单元/源文件,而不是在其他 TLU/文件中。

“如果对象或函数的文件范围标识符的声明包含存储类说明符static,则该标识符具有内部链接。30)”


  1. 只有在文件范围内,函数声明才能包含存储类说明符 static;见 6.7.1。

资料来源:C18,6.2.2/3


因此,Astatic函数只有当当且仅当:

  1. 您的程序包含多个翻译单元/源文件(.c.cpp)。

  1. 您希望将函数的范围限制在定义特定函数的文件中。

如果这两个要求不匹配,则您无需费心将函数限定为static.


旁注:

  • 如前所述,static函数在 C 和 C++ 之间完全没有区别,因为这是 C++ 继承自 C 的一个特性。

无关紧要的是,在 C++ 社区中,与使用未命名static的命名空间相比,合格函数的贬值存在令人心碎的争论,首先由 C++03 标准中的错误段落初始化,声明使用不推荐使用的静态函数很快由委员会自己修改并在 C++11 中删除。

这受到各种 SO 问题的影响:

未命名/匿名命名空间与静态函数

未命名命名空间优于静态命名空间?

为什么未命名的命名空间是静态的“优越”替代品?

弃用 static 关键字...不再?

事实上,它还没有按照 C++ 标准被弃用。因此,static函数的使用仍然是合法的。即使未命名的命名空间有优势,关于在 C++ 中使用或不使用静态函数的讨论仍受制于一个人的想法(基于意见),并且不适合本网站。

于 2020-06-08T12:44:53.593 回答
8

静态函数是可以在类本身上调用的函数,而不是类的实例。

例如,非静态将是:

Person* tom = new Person();
tom->setName("Tom");

此方法适用于类的实例,而不是类本身。但是,您可以拥有一个无需实例即可工作的静态方法。这有时用于工厂模式:

Person* tom = Person::createNewPerson();
于 2009-02-17T18:28:32.880 回答
7

次要问题:静态函数对翻译单元是可见的,对于大多数实际情况,翻译单元是定义函数的文件。您遇到的错误通常被称为违反单一定义规则。

该标准可能会说:

“每个程序都应包含该程序中使用的每个非内联函数或对象的一个​​定义;不需要诊断。”

这就是 C 语言看待静态函数的方式。然而,这在 C++ 中已被弃用。

此外,在 C++ 中,您可以将成员函数声明为静态的。这些主要是元功能,即它们不描述/修改特定对象的行为/状态,而是作用于整个类本身。此外,这意味着您不需要创建对象来调用静态成员函数。此外,这也意味着,您只能从此类函数中访问静态成员变量。

我将在 Parrot 的示例中添加基于这种静态成员函数的单例模式,以在程序的整个生命周期中获取/使用单个对象。

于 2009-02-17T18:50:57.117 回答
7

静态函数的答案取决于语言:

1) 在像 C 这样没有 OOPS 的语言中,这意味着该函数只能在其定义的文件中访问。

2)在像 C++ 这样具有 OOPS 的语言中,这意味着可以直接在类上调用该函数,而无需创建它的实例。

于 2016-03-06T13:31:35.497 回答
3

由于静态函数仅在此文件中可见。实际上,如果您将某个函数声明为“静态”,编译器可以为您做一些优化。

这是一个简单的例子。

主程序

#include <stdio.h>

static void test() 
{
    ghost(); // This is an unexist function.
}

int main()
{
    int ret = 0;

#ifdef TEST
#else
    test();
#endif
    return (ret);
} 

并编译

gcc -o main main.c

你会看到它失败了。因为你甚至没有实现 ghost() 函数。

但是,如果我们使用以下命令怎么办。

gcc -DTEST -O2 -o main main.c

成功了,这个程序就可以正常执行了。

为什么?有3个关键点。

  1. -O2 :编译器优化级别至少为 2。
  2. -DTEST : 定义 TEST,所以 test() 不会被调用。
  3. 将“静态”定义为 test()。

只有这三个条件都满足,才能通过编译。由于这个“静态”声明,编译器可以确认 test() 永远不会在其他文件中被调用。您的编译器可以在编译时删除 test()。由于我们不需要 test(),ghost() 是定义还是实现都没有关系。

于 2020-05-08T08:08:43.097 回答