问题是关于普通的 c 函数,而不是 c++ static
方法,正如评论中所阐明的那样。
我了解什么是 static
变量,但什么是 static
函数?
为什么如果我声明一个函数,比如说void print_matrix
,比如说a.c
(没有a.h
)并包含"a.c"
- 我得到"print_matrix@@....) already defined in a.obj"
,但是如果我将它声明为static void print_matrix
然后它编译?
更新只是为了澄清问题 - 正如你们中的许多人指出的那样,我知道包含 .c
是不好的。我只是暂时清除 main.c
中的空间,直到我更好地了解如何将所有这些函数分组到正确的 .h
和 .c
文件中。只是一个临时的、快速的解决方案。
static
函数是仅对同一文件中的其他函数可见的函数(更准确地说是相同的 translation unit)。
编辑:对于那些认为问题的作者意味着“类方法”的人:由于问题被标记为 C
,他的意思是一个普通的旧 C 函数。对于 (C++/Java/...) 类方法,static
表示可以在类本身上调用此方法,不需要该类的实例。
中的静态函数和 C++ 中的静态成员函数之间存在很大差异。在 C 语言中,静态函数在其编译单元之外是不可见的,这是编译到的目标文件。换句话说,将函数设为静态会限制其范围。您可以将静态函数视为其 *.c 文件的“私有”函数(尽管这并不完全正确)。
在 C++ 中,“静态”也可以应用于类的成员函数和数据成员。静态数据成员也称为“类变量”,而非静态数据成员是“实例变量”。这是 Smalltalk 术语。这意味着只有一个静态数据成员的副本由类的所有对象共享,而每个对象都有自己的非静态数据成员副本。所以静态数据成员本质上是一个全局变量,即类的成员。
非静态成员函数可以访问类的所有数据成员:静态和非静态。静态成员函数只能对静态数据成员进行操作。
考虑这一点的一种方法是,在 C++ 中,静态数据成员和静态成员函数不属于任何对象,而是属于整个类。
最小可运行多文件范围示例
在这里,我说明 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;
}
编译并运行:
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 draft 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
的完美选择。
没有静态的函数是 STB_GLOBAL
,规范说:
当链接编辑器组合几个可重定位的目标文件时,它不允许同名的 STB_GLOBAL 符号的多个定义。
这与多个非静态定义上的链接错误一致。
如果我们用 -O3
加速优化,sf
符号会完全从符号表中删除:它无论如何都不能从外部使用。 TODO 为什么在没有优化的情况下将静态函数保留在符号表上?它们可以用于任何事情吗?
也可以看看
变量相同:https://stackoverflow.com/a/14339047/895245
extern 是 static 的反面,函数默认已经是 extern 了:How do I use extern to share variables between source files?
C++ 匿名命名空间
在 C++ 中,您可能希望使用匿名命名空间而不是静态命名空间,这可以达到类似的效果,但进一步隐藏了类型定义:Unnamed/anonymous namespaces vs. static functions
void f() { puts("sf"); }
(即 f()
的两个定义)会导致未定义的行为,无需诊断。实际看到错误消息是链接器质量问题。
以下是关于普通 C 函数的 - 在 C++ 类中,修饰符 'static' 有另一个含义。
如果您只有一个文件,则此修饰符绝对没有区别。不同之处在于具有多个文件的较大项目:
在 C 中,每个“模块”(sample.c 和 sample.h 的组合)都是独立编译的,然后每个编译的目标文件(sample.o)都通过链接器链接到一个可执行文件。
假设您有几个文件包含在主文件中,其中两个有一个仅为方便起见在内部使用的函数,称为 add(int a, b)
- 编译器很容易为这两个模块创建目标文件,但链接器会抛出一个错误,因为它找到了两个具有相同名称的函数并且它不知道应该使用哪个函数(即使没有什么要链接的,因为它们没有在其他地方使用,而是在它自己的文件中)。
这就是为什么您将这个仅在内部使用的函数设为静态函数的原因。在这种情况下,编译器不会为链接器创建典型的“你可以链接这个东西”——标志,这样链接器就看不到这个函数,也不会产生错误。
静态函数定义会将此符号标记为内部符号。所以它对于从外部链接是不可见的,而只对同一个编译单元中的函数是可见的,通常是同一个文件。
第一:在另一个文件中包含 .cpp
文件通常是个坏主意 - 它会导致这样的问题 :-) 通常的方法是创建单独的编译单元,并为包含的文件添加头文件。
第二:
C++ 在这里有一些令人困惑的术语——直到在评论中指出,我才知道它。
a) static functions
- 继承自 C,以及您在此处讨论的内容。任何课外。静态 function 意味着它在当前编译单元之外不可见 - 因此在您的情况下 a.obj 有一个副本,而您的其他代码有一个独立的副本。 (使用多个代码副本使最终可执行文件膨胀)。
b) static member function
- 什么是面向对象的静态方法。住在一个班级里。您使用类而不是通过对象实例调用它。
这两个不同的静态函数定义是完全不同的。小心——这里是龙。
“什么是 C 中的‘静态’函数?”
让我们从头开始。
这一切都基于一个叫做“链接”的东西:
“在不同范围或同一范围内多次声明的标识符可以通过称为链接的过程来引用相同的对象或函数。29)有三种链接:外部,内部和无。
资料来源:C18,6.2.2/1
“在构成整个程序的一组翻译单元和库中,具有外部链接的特定标识符的每个声明都表示相同的对象或函数。在一个翻译单元中,具有内部链接的标识符的每个声明都表示相同的对象或函数. 每个没有链接的标识符声明都表示一个唯一的实体。
资料来源:C18,6.2.2/2
如果在没有存储类说明符的情况下定义函数,则该函数extern
默认具有所有链接:
“如果函数标识符的声明没有存储类说明符,则它的链接将完全确定,就好像它是使用存储类说明符 extern 声明的一样。”
资料来源:C18,6.2.2/5
这意味着 - 如果您的程序包含多个翻译单元/源文件(.c
或 .cpp
) - 该功能在您的程序拥有的所有 个翻译单元/源文件中可见。
在某些情况下,这可能是一个问题。如果您想使用 fe 两个不同的函数(定义),但在两个不同的上下文(实际上是文件上下文)中使用相同的函数名称怎么办。
在 C 和 C++ 中,static
存储类限定符应用于文件范围内的函数(不是 C++ 中类的 静态成员函数 或另一个类中的函数block) 现在可以提供帮助,并表示相应的函数仅在定义它的翻译单元/源文件内部可见,而在其他 TLU/文件中不可见。
“如果对象或函数的文件范围标识符的声明包含存储类说明符 static,则标识符具有内部链接。30)”
只有在文件范围内,函数声明才能包含存储类说明符 static;见 6.7.1。
资料来源:C18,6.2.2/3
因此,static
函数只有当且仅当:
您的程序包含多个翻译单元/源文件(.c 或 .cpp)。
和
您希望将函数的范围限制在定义特定函数的文件中。
如果不是两个这些要求都匹配,则您无需费力地将函数限定为 static
。
旁注:
如前所述,静态函数在 C 和 C++ 之间完全没有区别,因为这是 C++ 从 C 继承的特性。
没关系,在 C++ 社区中,与使用 未命名的命名空间 相比,将限定函数贬低为 static
的争论令人心碎,首先由C++03 标准,声明不推荐使用静态函数,委员会很快对其进行了修订并在 C++11 中删除。
这受到各种 SO 问题的影响:
Unnamed/anonymous namespaces vs. static functions
Superiority of unnamed namespace over static?
Why an unnamed namespace is a "superior" alternative to static?
Deprecation of the static keyword... no more?
事实上,它还没有按照 C++ 标准被弃用。因此,使用 static
函数仍然是合法的。即使 未命名的命名空间 有优势,关于在 C++ 中使用或不使用静态函数的讨论仍受制于一个人的想法(基于意见),并且不适合本网站。
静态函数是可以在类本身上调用的函数,而不是类的实例。
例如,非静态将是:
Person* tom = new Person();
tom->setName("Tom");
此方法适用于类的实例,而不是类本身。但是,您可以拥有一个无需实例即可工作的静态方法。这有时用于工厂模式:
Person* tom = Person::createNewPerson();
次要问题:静态函数对翻译单元是可见的,对于大多数实际情况,翻译单元是定义函数的文件。您遇到的错误通常被称为违反单一定义规则。
该标准可能会说:
“每个程序都应包含该程序中使用的每个非内联函数或对象的一个定义;不需要诊断。”
这就是 C 语言看待静态函数的方式。然而,这在 C++ 中已被弃用。
此外,在 C++ 中,您可以将成员函数声明为静态的。这些主要是元功能,即它们不描述/修改特定对象的行为/状态,而是作用于整个类本身。此外,这意味着您不需要创建对象来调用静态成员函数。此外,这也意味着,您只能从此类函数中访问静态成员变量。
我将在 Parrot 的示例中添加基于这种静态成员函数的单例模式,以在程序的整个生命周期中获取/使用单个对象。
静态函数的答案取决于语言:
1) 在像 C 这样没有 OOPS 的语言中,这意味着该函数只能在其定义的文件中访问。
2)在像 C++ 这样具有 OOPS 的语言中,这意味着可以直接在类上调用该函数,而无需创建它的实例。
static
限定的函数也具有文件范围,就像在 C 中一样。
由于静态函数仅在此文件中可见。实际上,如果您将某个函数声明为“静态”,编译器可以为您做一些优化。
这是一个简单的例子。
主程序
#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个关键点。
-O2 :编译器优化级别至少为 2。 -DTEST :定义 TEST,因此不会调用 test()。将“静态”定义为 test()。
只有这三个条件都满足,才能通过编译。由于这个“静态”声明,编译器可以确认 test() 永远不会在其他文件中被调用。您的编译器可以在编译时删除 test()。由于我们不需要 test(),ghost() 是定义还是实现都没有关系。
C 中的静态函数是作用域仅限于目标文件的函数。这意味着静态函数仅在其目标文件中可见。通过在函数名称前放置一个 static 关键字,可以将函数表示为静态函数。
一个证明这一点的例子如下 -
有两个文件 first.c
和 second.c
。这些文件的内容如下 -
first.c
的内容,
static void staticFunction(void)
{
printf("Static function staticFunction() ");
}
second.c
的内容,
int main()
{
staticFunction();
return 0;
}
现在,如果上面的代码被编译,就会出现错误,也就是说"undefined reference to staticFunction ()."
出现这种情况是因为staticFunction()函数是一个静态函数,只在其目标文件中可见。
用 C 语言演示静态函数的程序如下 -
#include <stdio.h>
static void staticFunction(void){
printf("Static function staticFunction() ");
}
int main()
{
staticFunction();
return 0;
}
上述程序的输出如下 -
Static function staticFunc()
在上面的程序中,函数 staticFunction()
是一个打印 "Static function staticFunction()"
的静态函数。 main()
函数称为 staticFunction()
。该程序正确执行,因为静态函数仅由其目标文件调用。