提问



问题是关于普通c函数,而不是c ++ static方法,正如评论中所阐明的那样。/questions/tagged/c /questions/tagged/c%2b%2b


好的,我理解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函数是仅对同一文件中的其他函数可见的函数(更准确地说,相同的翻译单元)。[53]


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

其它参考1


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


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


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


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

其它参考2


关于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


最小可运行多文件示例


交流转换器:


#include 

/*
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 

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 f
main sf
main f
a sf


解释



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

  • 有一个共享功能f



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


在C编程中,文件通常用于表示类,static函数表示类的私有方法。


一个常见的C模式是将this结构作为第一个方法参数传递,这基本上是C ++在幕后所做的。


标准说什么


C99 N1256草案6.7.1存储类说明符表示static是存储类说明符。[54]


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:[55]



??STB_LOCAL在包含其定义的目标文件外部不显示本地符号。多个文件中可能存在同名的本地符号,而不会相互干扰



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


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



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



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


如果我们用-O3进行优化,则sf符号将完全从符号表中删除:无论如何都不能从外部使用它。 TODO为什么在没有优化时根据符号表保留静态函数?他们可以用于任何事情吗?


另见



  • 变量相同:https://stackoverflow.com/a/14339047/895245

  • externstatic相反,默认情况下函数已经extern:如何使用extern在源文件之间共享变量?



亲自尝试


你可以在GitHub上玩的例子。[58]

其它参考4


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


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


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


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


这就是为什么你创建这个函数,它只用于内部,一个静态函数。在这种情况下,编译器不会为链接器创建典型的您可以链接此事物-flag,因此链接器不会看到此函数并且不会生成错误。

其它参考5


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


其次:


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


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


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


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

其它参考6


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

其它参考7


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


例如,非静态将是:


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


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


Person* tom = Person::createNewPerson();

其它参考8


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


标准可能会说:



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



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


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


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

其它参考9


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


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


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

其它参考10


对于c中的静态函数,compiller不会在堆栈上创建其内部变量,因此静态函数调用更快,因此您不能使用初始化函数,如:char c =A。