什么是“静态”?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文件中。只是一个临时的,快速的解决方案。

682568 次浏览

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

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

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

例如,一个非静态变量是:

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

此方法作用于类的实例,而不是类本身。然而,你可以有一个静态方法,它可以在没有实例的情况下工作。有时会在Factory模式中使用:

Person* tom = Person::createNewPerson();

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

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

其次:

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

a) static functions -继承自C,以及你在这里谈论的内容。在任何课堂之外。静态的函数意味着它在当前编译单元之外是不可见的——所以在你的例子中,a.b obj有一个副本,而你的其他代码有一个独立的副本。(在最终的可执行文件中添加多个代码副本)。

b) static member function -对象定向术语静态方法。存在于类中。您可以使用类而不是通过对象实例调用它。

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

C中的静态函数和c++中的静态成员函数之间有很大的区别。在C语言中,静态函数在其翻译单元(即它被编译到的目标文件)之外是不可见的。换句话说,使函数静态限制了它的作用域。您可以将静态函数视为其*.c文件的“私有”(尽管严格来说这并不正确)。

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

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

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

小提示:静态函数对于翻译单元是可见的,在大多数实际情况下,翻译单元是定义函数的文件。您所得到的错误通常被称为违反了一个定义规则。

标准可能是这样说的:

"每个程序应该包含每个非内联的确切定义 程序中使用的函数或对象;没有诊断 必需的。”< / p >

这是C语言看待静态函数的方式。然而,这在c++中是不推荐的。

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

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

下面是关于普通C函数的——在c++类中,修饰符“static”另有含义。

如果你只有一个文件,这个修饰符完全没有区别。不同之处在于有多个文件的大型项目:

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

假设你有几个文件,包括在主文件和两个内部的函数仅用于方便叫# EYZ0 -编译器会轻松地创建对象文件这两个模块,但链接器将抛出一个错误,因为它找到具有相同名称的两个函数,它不知道应该使用哪一个(即使没有什么联系,因为他们不习惯别的地方,但在它自己的文件中)。

这就是为什么这个函数,只在内部使用,是一个静态函数。在这种情况下,编译器不会为链接器创建典型的“you can link this thing”-标志,这样链接器就不会看到这个函数,也就不会产生错误。

最小可运行的多文件作用域示例

这里我将说明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();
}

c

#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;
}

# EYZ0。

编译并运行:

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++在底层所做的。

这是什么标准啊

6.7.1“Storage-class specifiers”说static是一个“Storage-class specifier”。

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在ELF规范中被记录在http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html:

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

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

不带static的函数是STB_GLOBAL,规范说:

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

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

如果我们使用-O3进行优化,sf符号将完全从符号表中删除:它无论如何都不能从外部使用。当没有优化时,为什么要在符号表上保持静态函数?它们有什么用途吗?

另请参阅

c++匿名命名空间

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

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

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

2)在c++等带有oop的语言中,这意味着可以直接在类上调用函数,而无需创建它的实例。

因为静态函数只在这个文件中可见。 实际上,编译器可以做一些优化为你,如果你声明“静态”的一些函数。< / p >

这里有一个简单的例子。

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个关键点。

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

只有这3个条件都为真,才能通过编译。 由于这个“静态”声明,编译器可以确认test()将永远不会在其他文件中被调用。编译器可以在编译时删除test()。因为我们不需要test(),所以是否定义或实现ghost()并不重要

“# EYZ1"

让我们从头说起。

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

“# EYZ0"

来源:C18, 6.2.2/1


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

来源:C18, 6.2.2/2


如果函数定义时没有存储类说明符,则该函数默认具有external链接:

如果一个函数的标识符声明没有存储类说明符,那么它的链接就像声明存储类说明符走读生一样确定。

来源:C18, 6.2.2/5

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

在某些情况下,这可能是个问题。如果你想使用f.e.两个不同的函数(定义),但在两个不同的上下文中(实际上是文件-上下文)使用相同的函数名,该怎么办?

在c# EYZ1 c++中,static存储类限定符应用于文件范围内的函数(而不是c++中类的静态成员函数或另一个块中的函数)现在可以提供帮助,并表示各自的函数只在定义它的翻译单元/源文件中可见,而在其他tlu /文件中不可见。

如果对象或函数的文件作用域标识符声明中包含存储类说明符静态,则该标识符具有内部链接。30)“;


  1. 函数声明只有在文件范围内才可以包含存储类说明符静态;6.7.1见。

来源:C18, 6.2.2/3


因此,static函数只有在以下情况下才有意义:

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

而且

  1. 您希望将函数的作用域限制在定义特定函数的文件中。

如果这些要求中这两个不匹配,则不需要考虑将函数限定为static


边注:

  • 正如前面提到的,static函数在C和c++之间绝对有完全没有区别,因为这是c++从C继承来的特性。

在c++社区中,有一场令人心碎的争论是,与使用不愿透露姓名的命名空间相比,static是不合适的资格函数。不愿透露姓名的命名空间最初是由c++ 03标准中一个错误的段落初始化的,声明静态函数的使用是不合适的,很快委员会自己修订了这一点,并在c++ 11中删除了这一点。

这涉及到各种SO问题:

未命名/匿名名称空间vs.静态函数

未命名命名空间优于静态命名空间?< / >

为什么一个未命名的命名空间是“superior”;静态的替代品?< / >

static关键字弃用…没有更多的?< / >

事实上,根据c++标准,它还没有被弃用。因此,使用static函数仍然是合法的。即使不愿透露姓名的命名空间有优势,关于在c++中使用或不使用静态函数的讨论是一个人的想法(基于意见),不适合这个网站。