关于C#:什么是“静态”功能?

What is a “static” function?

问题是关于C函数,而不是C++ EDCOX1,0种方法,如注释中所阐明的。

好的,我知道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函数是仅对同一文件中的其他函数可见的函数(更精确地说是同一翻译单元)。

编辑:对于那些认为问题的作者意味着"类方法"的人:当问题被标记为C时,他意味着一个普通的旧C函数。对于(C++/爪哇/…)类方法,EDCOX1〔0〕意味着该方法可以在类本身上调用,而不需要该类的实例。


C中的静态函数和C++中的静态成员函数之间有很大的区别。在C语言中,静态函数在其翻译单元(即编译到其中的对象文件)之外是不可见的。换句话说,使一个函数成为静态的限制了它的范围。您可以将静态函数视为它的*.c文件的"私有"(尽管这并不完全正确)。

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

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

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


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

第一种方法是将函数标记为具有内部链接,这样它就不能在其他翻译单元中引用。这种用法在C++中是不适用的。此用法首选未命名的命名空间。

1
2
3
4
5
6
7
8
9
10
11
12
// inside some .cpp file:

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

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

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


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

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

A.C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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上游。

编译并运行:

1
2
3
4
gcc -c a.c -o a.o
gcc -c main.c -o main.o
gcc -o main main.o a.o
./main

输出:

1
2
3
4
main f
main sf
main f
a sf

解释

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

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

在C语言编程中,文件通常用来表示"类",而static函数表示类的"私有"方法。

一个常见的C模式是将EDCOX1的5个结构作为第一个"方法"参数传递,这基本上是C++在引擎盖下所做的。

标准怎么说呢

c99 n1256草案6.7.1"存储类说明符"表示static是"存储类说明符"。

6.2.2/3"标识符链接"表示static表示internal linkage表示:

If the declaration of a file scope identifier for an object or a function contains the storage-class specifier static, the identifier has internal linkage.

6.2.2/2表示,在我们的示例中,internal linkage的行为如下:

In the set of translation units and libraries that constitutes an entire program, each declaration of a particular identifier with external linkage denotes the same object or function. Within one translation unit, each declaration of an identifier with internal linkage denotes the same object or function.

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

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

STB_LOCAL结合。

如果我们编译:

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

将符号表分解为:

1
readelf -s main.o

输出包括:

1
2
3
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上:

STB_LOCAL Local symbols are not visible outside the object file containing their definition. Local symbols of the same name may exist in multiple files without interfering with each other

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

没有静态的函数是STB_GLOBAL,规范中说:

When the link editor combines several relocatable object files, it does not allow multiple definitions of STB_GLOBAL symbols with the same name.

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

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

也见

  • 变量相同:https://stackoverflow.com/a/14339047/895245
  • externstatic相反,默认情况下函数已经是extern:如何使用extern在源文件之间共享变量?

C++匿名命名空间

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


下面是关于C函数的简单说明——在C++类中,修饰符"static"有另一个含义。

如果只有一个文件,那么这个修改器就没有任何区别。不同之处在于具有多个文件的大型项目:

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

假设您有几个包含在主文件中的文件,其中两个文件的函数只在内部使用,方便称为add(int a, b)—编译器很容易为这两个模块创建对象文件,但是链接器会抛出一个错误,因为它发现两个同名的函数,并且不知道是哪个函数它应该使用一个(即使没有链接,因为它们不是在其他地方使用,而是在它自己的文件中)。

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


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

其次:

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

a)static functions—继承自c,你在这里说的是什么。在任何班级之外。静态函数意味着它在当前编译单元之外是不可见的——所以在您的例子中,a.obj有一个副本,而您的其他代码有一个独立的副本。(用多个代码副本填充最终可执行文件)。

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

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


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


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

例如,非静态的将是:

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

此方法在类的实例上工作,而不是类本身。但是,您可以有一个静态方法,它可以在没有实例的情况下工作。这有时用于工厂模式:

1
Person* tom = Person::createNewPerson();


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

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

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


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

标准可能会说:

"Every program shall contain exactly one definition of every noninline
function or object that is used in that program; no diagnostic
required."

这是查看静态函数的C方法。然而,这在C++中是不受欢迎的。

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

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