内联函数中的静态变量

static variables in an inlined function

本文关键字:静态 变量 函数      更新时间:2023-10-16

我有一个在头文件中声明和定义的函数。这本身就是一个问题。当该函数未内联时,使用该标头的每个翻译单元都会获得该函数的副本,当它们链接在一起时,就会重复。我通过使函数内联来"修复"它,但恐怕这是一个脆弱的解决方案,因为据我所知,即使您指定了"inline"关键字,编译器也不能保证内联。如果这不是真的,请纠正我。

无论如何,真正的问题是,这个函数中的静态变量会发生什么?我最终会得到多少份?

我想你在这里错过了一些东西。

静态函数?

声明函数静态将使其"隐藏"在其编译单元中。

具有命名空间作用域 (3.3.6( 的名称具有内部链接,如果它是

— 显式声明为静态的变量、函数或函数模板;

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(

在标头中,它有一个有趣的副作用:内联函数可以在同一模块中多次定义,链接器将简单地将"它们"连接成一个(如果由于编译器的原因没有内联它们(。

对于内部声明的静态变量,标准特别指出有一个,而且只有一个:

extern 内联函数中的静态局部变量始终引用同一对象。

7.1.2/4 - C++98/C++14 (n3797(

(函数默认是 extern,因此,除非您特别将函数标记为静态,否则这适用于该函数(

这具有"静态"的优点(即可以在标头中定义(,没有缺陷(如果不内联,它最多存在一次(

静态局部变量?

静态

局部变量没有链接(它们不能在其范围之外通过名称引用(,但具有静态存储持续时间(即它是全局的,但它的构造和销毁遵循特定的规则(。

静态+内联?

混合内联

和静态将产生你所描述的后果(即使函数是内联的,里面的静态变量也不会是内联的,你最终会得到与编译单元一样多的静态变量,包括静态函数的定义(。

对作者补充问题的回答

自从我写了这个问题以来,我用Visual Studio 2008尝试了一下。我试图打开使VS符合标准的所有选项,但我可能错过了一些。这些是结果:

当函数只是"内联"时,静态变量只有一个副本。

当函数为"静态内联"时,副本数与翻译单元数一样多。

现在真正的问题是事情是否应该这样,或者这是否是Microsoft C++编译器的特质。

所以我想你有这样的东西:

void doSomething()
{
   static int value ;
}

你必须意识到,函数内部的静态变量,简单地说,一个全局变量,除了函数的作用域之外,对所有变量都隐藏,这意味着只有声明它的函数才能访问它。

内联函数不会更改任何内容:

inline void doSomething()
{
   static int value ;
}

将只有一个隐藏的全局变量。编译器将尝试内联代码的事实不会改变只有一个全局隐藏变量的事实。

现在,如果您的函数被声明为静态:

static void doSomething()
{
   static int value ;
}

然后,对于每个编译单元,它是"私有的",这意味着每个 CPP 文件(包括声明静态函数的标头(都将拥有自己的函数私有副本,包括其自己的全局隐藏变量的私有副本,因此有多少个编译单元包含标头,就有多少变量。

将"内联"添加到"静态"函数中,其中包含"静态"变量:

inline static void doSomething()
{
   static int value ;
}

就内部的静态变量而言,与不添加此"内联"关键字具有相同的结果。

所以VC++的行为是正确的,你误解了"内联"和"静态"的真正含义。

我相信

编译器会创建变量的许多副本,但链接器会选择一个并使所有其他副本都引用它。 当我尝试创建不同版本的内联函数的实验时,我得到了类似的结果;如果函数实际上未内联(调试模式(,则所有调用都将转到同一函数,而不考虑从哪个源文件调用它们。

像编译器一样思考一下 - 否则怎么可能? 每个编译单元(源文件(都独立于其他单元,可以单独编译;因此,每个人都必须创建变量的副本,认为它是唯一的副本。 链接器能够跨越这些边界并调整变量和函数的引用。

我发现Mark Ransom的答案很有帮助 - 编译器创建静态变量的许多副本,但链接器选择一个并在所有翻译单元中强制执行它。

在其他地方,我发现了这个:

参见 [dcl.fct.spec]/4

[..]具有外部链接的内联功能应具有相同的功能 所有翻译单元中的地址。外部中的静态局部变量 内联函数始终引用同一对象。字符串文本 extern 内联函数是不同翻译单元中的同一对象。

我没有要检查的标准副本,但它与我在VS Express 2008中检查程序集的经验相匹配

应该是

这样的。"static"告诉编译器您希望函数是编译单元的本地函数,因此您希望每个编译单元一个副本,每个函数实例一个静态变量副本。

">

inline"用于告诉编译器您希望函数被内联;如今,它只是将其视为"如果代码有多个副本没关系,只要确保它是相同的函数即可"。所以每个人都共享静态变量。

注:本回答是针对原发帖人发给自己的回答而写的。

自从我写了这个问题后,我就用Visual Studio 2008尝试了一下。我试图打开使VS符合标准的所有选项,但我可能错过了一些。这些是结果:

当函数只是"内联"时,静态变量只有一个副本。

当函数为"静态内联"时,副本数与翻译单元数一样多。

现在真正的问题是事情是否应该是这样的,或者这是否是Microsoft C++编译器的意识形态。

内联意味着可执行代码(指令(内联到调用函数的代码中。 编译器可以选择这样做,而不管你是否要求它这样做。 这对函数中声明的变量(数据(没有影响。

静态意味着一个副本分布在整个程序中,但内联意味着它在同一程序中需要多次相同的代码,因此不可能在内联函数中使变量静态。

我相信

你最终会得到每个翻译单元一个。您实际上已经获得了该函数的许多版本(及其声明的静态变量(,每个包含标头的翻译单元都有一个版本。

除了任何设计问题之外,这一切都可能意味着,因为你已经坚持使用它,在这种情况下你应该使用静态而不是内联。这样,每个人都共享相同的变量。(静态功能(