我有一个在头文件中声明和定义的函数.这本身就是一个问题.如果未内联该函数,则使用该标头的每个翻译单元都会获得该函数的副本,并且当它们链接在一起时会重复.我通过使函数内联来"修复",但我担心这是一个脆弱的解决方案,因为据我所知,即使指定了"inline"关键字,编译器也不保证内联.如果不是这样,请纠正我.
无论如何,真正的问题是,这个函数里面的静态变量会发生什么?我最终会收到多少份?
我想你错过了什么,这里.
声明一个静态函数会使其在其编译单元中"隐藏".
具有命名空间作用域(3.3.6)的名称具有内部链接(如果它的名称)
- 显式声明为static的变量,函数或函数模板;
3.5/3 - C++ 14(n3797)
当名称具有内部链接时,其表示的实体可以通过同一翻译单元中其他范围的名称来引用.
3.5/2 - C++ 14(n3797)
如果在标头中声明此静态函数,则包含此标头的所有编译单元都将拥有自己的函数副本.
问题是,如果该函数内部存在静态变量,则包含此标题的每个编译单元也将拥有自己的个人版本.
将其声明为内联使其成为内联的候选者(这在C++中并不意味着很多,因为编译器将内联或不内联,有时忽略关键字内联存在或不存在的事实):
带有内联说明符的函数声明(8.3.5,9.3,11.3)声明了一个内联函数.内联说明符向实现指示在调用点处函数体的内联替换优先于通常的函数调用机制.在呼叫点执行此内联替换不需要实现; 但是,即使省略了这种内联替换,仍应遵守7.1.2定义的内联函数的其他规则.
7.1.2/2 - C++ 14(n3797)
在头文件中,它有一个有趣的副作用:内联函数可以在同一个模块中多次定义,链接器只需将"它们"连接成一个(如果它们没有为编译器的原因而内联).
对于在里面声明的静态变量,标准特别指出那里有一个,而且只有一个:
外部内联函数中的静态局部变量始终引用同一个对象.
7.1.2/4 - C++ 98/C++ 14(n3797)
(默认情况下,函数是extern,因此,除非您将函数专门标记为static,否则这适用于该函数)
这具有"静态"的优点(即它可以在标题中定义)而没有其缺陷(如果没有内联,它最多存在一次)
静态局部变量没有链接(它们不能通过其范围之外的名称引用),但具有静态存储持续时间(即它是全局的,但其构造和销毁遵循特定规则).
混合内联和静态将产生您描述的结果(即使函数内联,内部的静态变量也不会,并且您将以与编译单元一样多的静态变量结束,包括静态函数的定义).
自从我写了这个问题后,我用Visual Studio 2008试了一下.我试图打开使VS符合标准的所有选项,但我可能错过了一些.这些是结果:
当函数仅仅是"内联"时,只有一个静态变量的副本.
当函数是"静态内联"时,存在与翻译单元一样多的副本.
真正的问题是现在是否应该采用这种方式,或者这是否是Microsoft C++编译器的特性.
所以我想你有类似的东西:
void doSomething() { static int value ; }
你必须意识到函数内部的静态变量,简单地说,一个全局变量隐藏到除函数范围之外的所有变量,这意味着只有它内部声明的函数才能到达它.
内联函数不会改变任何东西:
inline void doSomething() { static int value ; }
只有一个隐藏的全局变量.编译器将尝试内联代码的事实不会改变只有一个全局隐藏变量的事实.
现在,如果您的函数声明为static:
static void doSomething() { static int value ; }
然后它对于每个编译单元是"私有的",这意味着包含声明静态函数的头的每个CPP文件将具有其自己的函数的私有副本,包括其自己的全局隐藏变量的私有副本,因此具有尽可能多的变量.有包括标题的编译单元.
在"静态"函数中添加"内联",其中包含"静态"变量:
inline static void doSomething() { static int value ; }
就内部静态变量而言,与不添加此"inline"关键字具有相同的结果.
所以VC++的行为是正确的,你误解了"内联"和"静态"的真正含义.
我相信编译器会创建许多变量副本,但链接器会选择一个并使所有其他人引用它.当我尝试创建不同版本的内联函数时,我得到了类似的结果; 如果函数实际上没有内联(调试模式),则无论调用它们的源文件如何,所有调用都转到相同的函数.
一会儿就像编译器一样思考 - 怎么可能不这样呢?每个编译单元(源文件)独立于其他编译单元,可以单独编译; 因此,每个人都必须创建变量的副本,认为它是唯一的变量.链接器能够跨越这些边界并调整变量和函数的引用.
我发现Mark Ransom的答案很有用 - 编译器会创建静态变量的许多副本,但链接器会选择一个并在所有翻译单元中强制执行.
在其他地方我发现了这个:
见[dcl.fct.spec]/4
[..]具有外部链接的内联函数在所有翻译单元中应具有相同的地址.外部内联函数中的静态局部变量始终引用同一个对象.extern内联函数中的字符串文字是不同翻译单元中的相同对象.
I don't have a copy of the standard to check, but it matches with my experience examining the assembly in VS Express 2008
它应该是这样的."static"告诉编译器您希望函数对于编译单元是本地的,因此您希望每个编译单元有一个副本,并且每个函数实例需要一个静态变量副本.
"inline"用于告诉编译器您希望函数内联; 如今,它只是把它当作"如果有几个代码副本就可以了,只要确保它是相同的功能".所以每个人都分享静态变量.
注意:这个答案是针对原始海报贴在自己身上的答案而写的.