什么是C中的“静态”功能?


505

问题是关于平原 功能,不是 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文件。只是一个临时的快速解决方案。

Answers:


684

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

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


2
实际上,我没有将其标记为c ++,有些管理员可能对此进行了标记,但这与C ++有关,所以C ++有什么区别?
Slava V

16
C ++方法通常被称为“成员函数”,因此我同意C ++引入了一些歧义。这不是您的错-语言仅将关键字用于两种不同的事物。
Chuck

2
不,他仍然表示C ++函数。一个C ++自由函数,而不是一个C ++成员函数。
Lightness Races in Orbit

3
@Chuck:C ++术语从不使用“方法”一词;这是Java术语-在C ++标准文档中,它始终被称为“成员函数”(请参阅此答案C ++与Java术语词汇表(例如,C ++使用“数据成员”,而Java使用“字段”等))。
ShreevatsaR 2015年

6
为了澄清这个答案,该函数的名称仅在相同翻译单元的其他部分(在该名称的第一个声明下方)可见。可以通过其他方式(例如函数指针)从其他单元(和同一单元的较早部分)调用该函数。
MM

199

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

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

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

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


42
C ++也具有文件静态功能。无需为此引入C。
Lightness Races in Orbit

17
在C ++中,静态函数是静态函数。静态成员函数是静态成员函数,也称为方法。C没有成员的事实并不意味着函数是“ C”。
Gerasimos R

3
全局变量和类静态变量(名称空间除外)之间有什么区别?
亚历山大·马拉霍夫

3
名称空间是主要区别。另一个区别是您可以将静态数据成员设为私有,因此只能从类的成员函数中进行访问。换句话说,与全局变量相比,您对静态数据成员有更多的控制权。
Dima 2013年

2
有人可以解释为什么认为静态函数对其.c文件私有的说法并不严格正确吗?还有什么要说的?
YoTengoUnLCD

77

对于C ++中的函数,关键字static有两种用法。

首先是将功能标记为具有内部链接,因此无法在其他翻译单元中引用它。C ++不赞成使用此用法。对于这种用法,首选未命名的名称空间。

// inside some .cpp file:

static void foo();    // old "C" way of having internal linkage

// C++ way:
namespace
{
   void this_function_has_internal_linkage()
   {
      // ...
   }
}

第二种用法是在类的上下文中。如果一个类具有静态成员函数,则意味着该函数是该类的成员(并且具有对其他成员的通常访问权限),但是不需要通过特定的对象调用它。换句话说,在该函数内部,没有“ this”指针。


1
问题是关于c中的静态问题。
德庆

8
@Deqing,该问题最初被标记为C ++,作者谈论使用“ .cpp”文件。
Brian Neal

57

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

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

交流电

#include <stdio.h>

/* Undefined behavior: already defined in main.
 * Binutils 2.24 gives an error and refuses to link.
 * /programming/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();
}

main.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;
}

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

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

和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_LOCALhttp://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 ++中,您可能想使用匿名名称空间而不是静态名称空间,这可以达到类似的效果,但是会进一步隐藏类型定义:未命名/匿名名称空间与静态函数


3
注意:(void f() { puts("sf"); }即的两个定义f())会导致未定义的行为,而无需诊断。实际看到错误消息是链接器质量问题。
MM 2015年

2
这是最好,最准确的解释!比你!
Aqua

20

以下是有关普通C函数的信息-在C ++类中,修饰语“静态”具有另一种含义。

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

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

假设您有几个文件包含在主文件中,其中两个文件的功能仅供内部使用,以方便使用add(int a, b)-编译器可以轻松地为这两个模块创建目标文件,但是链接器会抛出错误,因为它找到两个具有相同名称的函数,并且不知道应使用哪个函数(即使没有任何链接,因为它们不在其他位置使用,而是在其自己的文件中使用)。

这就是为什么要使此函数仅在内部使用的静态函数。在这种情况下,编译器不会为链接程序创建典型的“您可以链接此内容”标志,以便链接程序看不到此功能,并且不会产生错误。


16

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

其次:

C ++这里有一些令人困惑的术语-直到在注释中指出,我才知道。

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

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

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


好吧,我这样做只是为了临时清除main.cpp中的一些空间,直到我决定如何将文件与适当的.hpp一起组织到库中为止。有更好的主意怎么做吗?
Slava V

1
C ++中正确的术语是成员函数,而不是方法。C ++ Legalese中没有“方法”。方法是一个通用的OO术语。C ++通过成员函数实现它们。
布赖恩·尼尔

14

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


7

静态函数是可以在类本身上调用的函数,与类的实例相反。

例如,非静态将是:

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

此方法对类的实例起作用,而不对类本身起作用。但是,您可以有一个无需实例即可工作的静态方法。有时在Factory模式下使用:

Person* tom = Person::createNewPerson();

2
在我看来,您是在谈论静态“方法”,而不是“功能”?
Slava V

我以为您是在类中引用静态函数。
鹦鹉

如果我知道“方法”在C ++中被称为“方法函数”,那我会更加清楚。好吧,现在我知道了:)仍然感谢
Slava V

5
C ++中没有“方法”,只有函数。C ++标准从不提及“方法”,而仅提及“函数”。
布赖恩·尼尔

1
@Puddle我知道您在说什么,但是在C ++标准中没有“方法”的定义。C ++仅具有各种功能。“方法”是一个通用的OO术语,在其他语言中以及非正式地在C ++中使用。一种方法在C ++中被正式称为“成员函数”。
布莱恩·尼尔

7

次要要点:静态功能对于翻译单元是可见的,在大多数实际情况下,静态功能是在其中定义功能的文件。您收到的错误通常称为违反“一个定义规则”。

该标准可能说:

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

这是查看静态函数的C方法。但是在C ++中不推荐使用此方法。

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

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


7

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

1)在没有C之类的OOPS的语言中,这意味着该功能只能在定义其的文件中访问。

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


这不是真的。第二段的说明是指类的“ 静态成员函数 ”,而不是“ 静态函数 ”。在C ++中,具有限定的函数static具有文件范围,同样,像它在C.
罗伯茨支持莫妮卡切廖

0

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

这是一个简单的例子。

main.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()定义“静态”。

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


0

C中的“ static”函数是什么?

让我们从头开始。

这全都基于一种称为“链接”的事物:

可以通过称为链接的过程使在不同作用域或相同作用域中声明的标识符多次引用相同的对象或功能。29)共有三种链接:外部,内部和无。

资料来源:C18,6.2.2 / 1


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

资料来源:C18,6.2.2 / 2


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

“如果一个函数的标识符声明没有存储类说明符,则其链接的确定就如同使用存储类说明符extern声明的一样。”

资料来源:C18,6.2.2 / 5

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

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

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

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


30)函数声明仅在文件范围内才可以包含存储类specifierstatic;参见6.7.1。

资料来源:C18,6.2.2 / 3


因此,static仅当A 函数有意义:

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

  2. 您希望将功能的范围限制为在其中定义了特定功能的文件。

如果这两个要求均不匹配,则无需费神就可以将函数限定为static


注意事项:

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.