内联函数中的静态变量


83

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

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

Answers:


105

我想您在这里缺少什么。

静态功能?

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

具有名称空间范围(3.3.6)的名称如果具有以下名称,则具有内部链接:

—明确声明为静态的变量,函数或函数模板;

3.5 / 3-C ++ 14(n3797)

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

3.5 / 2-C ++ 14(n3797)

如果您在标头中声明此静态函数,则包括该标头的所有编译单元都将拥有其自己的函数副本。

关键是,如果该函数中包含静态变量,则包含此标头的每个编译单元也将具有自己的个人版本。

内联函数?

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

具有内联说明符的函数声明(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文件(包括声明了静态函数的标头)都将拥有该函数的私有副本,包括其自身的全局隐藏变量的私有副本,因此与有包括头的编译单元。

在内部带有“ static”变量的“ static”函数中添加“ inline”:

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

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

因此,VC ++的行为是正确的,并且您误解了“内联”和“静态”的真实含义。


我认为您缺少要提的重要一点,即在链接阶段,将内联函数中声明的所有静态变量都解析为一个,对吗?
user14416 '16

1
否,因为每个静态变量都在其自己的单独函数内:尽管这些函数具有相同的名称,但它们具有内部链接,因此不会在翻译单元之间共享。
paercebal '16

1
@paercebal中inline void doSomething() { static int value ; },该功能具有外部链接;如果它出现在包含在两个不同单元中的标头中,则表示这是ODR违规行为
MM

@MM是什么意思?您的功能是inline,它不能违反ODR。
罗斯兰

@Ruslan,这是一个不重要的人物
MM

39

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

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


2
AFAICT,您在这里所说的完全正确。我不明白为什么人们不赞成这个答案。我唯一的猜测是,它们读到“变量的许多副本”,然后停止!:(反正从我令牌(+1)。
理查德·戈登

3
当人们询问编译器是什么时,它们表示编译器+链接器,因为您无法运行目标文件。因此,这个答案是正确的,但完全没有意义。
Evan Dark

1
因为人们无知。这是一个更高级的问题,应该在讨论中加以区分。
Sogartar

13

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

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

参见[dcl.fct.spec] / 4

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

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


5

应该是这种方式。“静态”告诉编译器您希望函数在编译单元本地,因此,您希望每个编译单元一个副本,并且每个函数实例一个静态变量副本。

“内联”用于告诉编译器您希望内联函数;如今,它只是因为“如果有多个代码副本,只需确保它具有相同的功能就可以了”。因此,每个人都共享静态变量。

注意:此答案是针对原始张贴者自己张贴的答案而写的。


1
他问的是“内联函数”中的“静态变量”,而不是静态函数中的变量。
理查德·科登

我们对此表示同意,但是您是对的:需要进行修改才能将答案重新显示在上下文中。
拉斐尔·圣皮埃尔

我也碰到过一点。那么这是哪两个呢?inline导致函数被内联或者可以有多个副本?
瓦西里斯18/12/31

@Vassilis都是正确的,尽管inline不会引起内联,它只是暗示了这一点,它允许多个定义(但不能在同一编译单元中)。
拉斐尔·圣·皮埃尔

3

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

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

当该功能为“静态内联”时,副本数与翻译单元数相同。

现在真正的问题是,是否应该采用这种方式,或者这是否是Microsoft C ++编译器的意识形态。


1
“当函数为“静态内联”时,”-您的原始帖子中没有提到任何有关此操作的信息。您应该获得不同的结果,因为函数的静态与变量的静态含义不同。函数上的static表示其他翻译单元将看不到此定义。
Windows程序员

不确定您的设置,但在这种情况下编译器可以正常工作。但是,您可能需要包括单元测试,以防将来遇到一些不合格的编译器。
罗伯特·古尔德

-1

内联表示将可执行代码(指令)内联到调用函数的代码中。不管您是否要求,编译器都可以选择执行此操作。这对函数中声明的变量(数据)没有影响。


-1

除了任何设计问题之外,这还可能意味着所有问题,因为您已经被它所困扰,在这种情况下,您应该使用static而不是inline。这样,每个人都共享相同的变量。(静态功能)


-2

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


-2

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.