什么是“静态”功能?

时间:2009-02-17 18:26:02

标签: c function static

问题是关于简单的函数,而不是 static方法,正如评论中所阐明的那样。

好的,我了解static变量是什么,但static函数是什么?

为什么如果我宣布一个函数,让我们说void print_matrix,让我们说a.c(没有a.h)并包括"a.c" - 我得"print_matrix@@....) already defined in a.obj" 1}},但是如果我将其声明为static void print_matrix那么它会编译吗?

更新只是为了清理 - 我知道包含.c的内容很糟糕,正如你们许多人指出的那样。我只是暂时清除main.c中的空格,直到我更好地了解如何将所有这些功能分组到正确的.h.c文件中。只是一个暂时的,快速的解决方案。

12 个答案:

答案 0 :(得分:612)

static函数是仅对同一文件中的其他函数可见的函数(更确切地说,相同的translation unit )。

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

答案 1 :(得分:185)

C中的静态函数与C ++中的静态成员函数之间存在很大差异。在C中,静态函数在其转换单元之外是不可见的,它是编译成的目标文件。换句话说,使函数静态限制其范围。您可以将静态函数视为其* .c文件的“私有”(虽然这不是严格正确的。)

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

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

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

答案 2 :(得分:73)

关于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”指针。

答案 3 :(得分:43)

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

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

的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 upstream

编译并运行:

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编程中,文件通常用于表示&#34;类&#34;和static函数代表&#34;私有&#34;班级的方法。

一个常见的C模式是传递this结构作为第一个&#34;方法&#34;这个论点基本上就是C ++所做的。

标准说什么

C99 N1256 draft 6.7.1&#34;存储类说明符&#34;说static是&#34;存储类说明符&#34;。

6.2.2 / 3&#34;标识符的链接&#34;说static暗示internal linkage

  

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

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

  

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

其中&#34;翻译单位&#34;是预处理后的源文件。

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为什么在没有优化时根据符号表保留静态函数?他们可以用于任何事情吗?

另见

C ++匿名命名空间

在C ++中,您可能希望使用匿名命名空间而不是静态命名空间,这会产生类似的效果,但会进一步隐藏类型定义:Unnamed/anonymous namespaces vs. static functions

答案 4 :(得分:19)

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

如果你只有一个文件,这个修饰符绝对没有区别。不同之处在于包含多个文件的更大项目:

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

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

这就是你制作这个只用于内部的静止功能的原因。在这种情况下,编译器不会为链接器创建典型的“您可以链接此事物”-flag,因此链接器不会看到此函数并且不会生成错误。

答案 5 :(得分:16)

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

其次:

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

a)static functions - 继承自C,你在这里谈论的是什么。任何课外。静态函数表示它在当前编译单元外部不可见 - 因此在您的情况下,a.obj具有副本,而您的其他代码具有独立副本。 (使用代码的多个副本来膨胀最终的可执行文件)。

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

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

答案 6 :(得分:14)

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

答案 7 :(得分:8)

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

例如,非静态将是:

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

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

Person* tom = Person::createNewPerson();

答案 8 :(得分:7)

静态功能的答案取决于语言:

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

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

答案 9 :(得分:6)

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

标准可能会说:

  

“每个节目都应包含每个非线条的一个定义   该程序中使用的函数或对象;没有诊断   要求。“

这是查看静态函数的C方式。但是,这在C ++中已弃用。

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

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

答案 10 :(得分:3)

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

让我们从头开始。

这全都基于一种叫做“链接”的东西:

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

来源:C18,6.2.2 / 1


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

来源:C18,6.2.2 / 2


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

“如果函数标识符的声明没有存储类说明符,则其链接的确定与使用存储类说明符 extern 声明时完全相同。”

来源:C18,6.2.2 / 5

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

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

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

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


  1. 仅当函数声明在文件范围内时,它才能包含存储类说明符。参见6.7.1。

来源:C18,6.2.2 / 3


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

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

  1. 您要将功能的范围限制为定义了特定功能的文件。

如果这些要求中的都不相同,则无需费心将功能限定为static


注意事项:

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

在C ++社区中,关于使用{em>命名空间而不是使用命名空间进行折旧的争论,关于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 ++中使用或不使用静态函数的讨论也要一个人(基于观点),并且不适合本网站。

答案 11 :(得分:1)

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

这是一个简单的例子。

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()都没关系。