我有一个在头文件中声明和定义的函数。这本身就是一个问题。如果未内联该函数,则使用该标头的每个翻译单元都会获得该函数的副本,并且当它们链接在一起时会重复。我通过使函数内联来“修复”,但我担心这是一个脆弱的解决方案,因为据我所知,即使指定了“inline”关键字,编译器也不保证内联。如果不是这样,请纠正我。
无论如何,真正的问题是,这个函数里面的静态变量会发生什么?我最终会收到多少份?
答案 0 :(得分:90)
我猜你错过了什么,这里。
声明一个静态函数会使其在其编译单元中“隐藏”。
具有命名空间范围(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)
在标题中,它有一个有趣的副作用:内联函数可以在同一个模块中多次定义,链接器只需将“它们”连接成一个(如果它们没有为编译器的原因而内联)。 / p>
对于在里面声明的静态变量,标准专门说明一个,只有一个:
extern内联函数中的静态局部变量始终引用同一个对象。
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”函数中,其中包含“static”变量:
inline static void doSomething()
{
static int value ;
}
与不添加此“inline”关键字具有相同的结果,就内部静态变量而言。
所以VC ++的行为是正确的,你误解了“内联”和“静态”的真正含义。
答案 1 :(得分:36)
我相信编译器会创建许多变量副本,但链接器会选择一个并使其他所有引用它。当我尝试创建不同版本的内联函数时,我得到了类似的结果;如果该函数实际上没有内联(调试模式),则无论调用它们的源文件如何,所有调用都转到同一函数。
暂时想想编译器 - 怎么会这样呢?每个编译单元(源文件)独立于其他编译单元,可以单独编译;因此,每个人都必须创建变量的副本,认为它是唯一的变量。链接器能够跨越这些边界并调整变量和函数的引用。
答案 2 :(得分:11)
我发现Mark Ransom的答案很有帮助 - 编译器会创建静态变量的许多副本,但链接器会选择一个并在所有翻译单元中强制执行。
在其他地方我发现了这个:
参见[dcl.fct.spec] / 4
[..]具有外部链接的内联函数应具有相同的功能 所有翻译单位的地址。 extern中的静态局部变量 内联函数始终引用同一个对象。一个字符串文字 extern inline函数是不同翻译单元中的同一对象。
我没有要检查的标准副本,但它符合我在VS Express 2008中检查程序集的经验
答案 3 :(得分:5)
应该是这样的。 “static”告诉编译器你希望函数是编译单元的本地函数,因此你需要每个编译单元一个副本和每个函数实例的一个静态变量副本。
“inline”用于告诉编译器您希望函数内联;如今,它只是把它当作“如果有几个代码副本就可以了,只要确保它是相同的功能”。所以每个人都分享静态变量。
注意:这个答案是针对原始海报发给自己的答案而写的。
答案 4 :(得分:3)
自从我写了这个问题以来,我尝试使用Visual Studio 2008.我试图打开使VS符合标准的所有选项,但我可能错过了一些。结果如下:
当函数仅仅是“内联”时,只有一个静态变量副本。
当函数是“静态内联”时,副本的数量与翻译单位一样多。
现在真正的问题是,事情是否应该是这样的,或者这是否是Microsoft C ++编译器的意识异性。
答案 5 :(得分:-1)
内联意味着可执行代码(指令)被内联到调用函数的代码中。无论您是否要求,编译器都可以选择这样做。这对函数中声明的变量(数据)没有影响。
答案 6 :(得分:-1)
除了任何设计问题,这一切都可能暗示,因为你已经坚持使用它,你应该在这种情况下使用静态而不是内联。这样每个人都分享相同的变量。 (静态功能)
答案 7 :(得分:-2)
我相信每个翻译单元最终会有一个。你已经有效地获得了该函数的许多版本(及其声明的静态变量),每个版本都包含一个包含标题的翻译单元。
答案 8 :(得分:-2)
静态意味着一个副本分布在整个程序中,但内联意味着它在同一个程序中需要多次使用相同的代码,因此不可能在内联函数中使变量成为静态。