91

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

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

4

9 回答 9

118

我猜你在这里遗漏了一些东西。

静态函数?

声明一个静态函数将使其在其编译单元中“隐藏”。

具有命名空间范围 (3.3.6) 的名称如果是

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

3.5/3 - C++14 (n3797)

当名称具有内部链接时,它所表示的实体可以由同一翻译单元中其他范围的名称引用。

3.5/2 - C++14 (n3797)

如果你在头文件中声明这个静态函数,那么包括这个头文件的所有编译单元都将拥有自己的函数副本。

问题是,如果该函数内有静态变量,则包括此头文件的每个编译单元也将有自己的个人版本。

内联函数?

将其声明为 inline 使其成为内联的候选对象(现在在 C++ 中这并不意味着很多,因为编译器会内联或不内联,有时会忽略关键字 inline 存在或不存在的事实):

带有 inline 说明符的函数声明(8.3.5、9.3、11.3)声明了一个内联函数。inline 说明符向实现表明,在调用点对函数体进行内联替换优于通常的函数调用机制。在调用点执行此内联替换不需要实现;然而,即使省略了这个内联替换,7.1.2 中定义的内联函数的其他规则仍应得到遵守。

7.1.2/2 - C++14 (n3797)

在头文件中,它有一个有趣的副作用:内联函数可以在同一个模块中定义多次,并且链接器将简单地将“它们”合并为一个(如果由于编译器的原因它们没有被内联)。

对于内部声明的静态变量,标准明确规定有一个,而且只有一个:

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

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

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

这具有“静态”的优点(即可以在标题中定义)而没有缺陷(如果没有内联,它最多存在一次)

静态局部变量?

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

静态+内联?

混合内联和静态将产生您所描述的后果(即使函数是内联的,内部的静态变量也不会,并且您将以编译单元的数量结束,包括静态函数的定义)。

回答作者的附加问题

自从我写了这个问题后,我就使用 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++的行为是正确的,你误会了“内联”和“静态”的真正含义。

于 2008-10-09T20:51:02.370 回答
43

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

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

于 2008-10-09T02:35:58.853 回答
12

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

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

见 [dcl.fct.spec]/4

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

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

于 2009-09-05T22:27:13.483 回答
7

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

"inline" 用来告诉编译器你希望函数被内联;现在,它只是认为“如果有多个代码副本就可以,只要确保它是相同的功能”。所以每个人都共享静态变量。

注意:此答案是针对原始发布者发布给自己的答案而写的。

于 2008-10-09T02:33:24.410 回答
3

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

当函数只是“内联”时,静态变量只有一份副本。

当函数是“静态内联”时,有多少个翻译单元就有多少个副本。

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

于 2008-10-09T02:19:25.390 回答
-1

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

于 2008-10-09T01:51:32.233 回答
-3

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

于 2008-10-09T01:47:40.963 回答
-3

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

于 2009-08-29T02:34:12.577 回答
-4

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

于 2008-10-09T02:15:13.857 回答