c++ 内联函数中的静态变量会发生什么?

klsxnrf1  于 9个月前  发布在  其他
关注(0)|答案(9)|浏览(150)

我有一个在头文件中声明和定义的函数。这本身就是一个问题。当该函数没有内联时,每个使用该头文件的翻译单元都会得到该函数的副本,当它们链接在一起时,就会出现重复。
我已经通过创建函数inline“修复”了这个问题,但我担心这是一个脆弱的解决方案,因为据我所知,编译器并不保证内联,即使你指定了inline关键字。

不管怎样,这个函数内部的静态变量会发生什么?我最终会有多少个副本?

mitkmikd

mitkmikd1#

我猜你漏掉了什么。

静态函数?

将函数声明为静态将使其在其编译单元中“隐藏”。
具有命名空间作用域(3.3.6)的名称具有内部链接,如果它是

  • 显式声明为静态的变量、函数或函数模板;
    3.5/3 - C14(n3797)中的命令行模式
    当名称具有内部链接时,它所表示的实体可以被同一翻译单元中其他作用域的名称引用。
    3.5/2 - C
    14(n3797)中的命令行模式
    如果您在信头中宣告这个静态函式,则所有包含这个信头的编译公寓都会有自己的函式复本。
    问题是,如果函数中有静态变量,则每个编译单元(包括该头)也将有自己的个人版本。

内联函数?

将其声明为inline使其成为内联的候选对象(现在在C中这并不意味着什么,因为编译器将内联或不内联,有时忽略关键字inline存在或不存在的事实):
一个函数声明(8.3.5,9.3,11.3)中使用内联说明符声明了一个内联函数。内联说明符向实现表明,在调用点对函数体的内联替换优于通常的函数调用机制。在调用点执行这种内联替换不需要实现;然而,即使省略了这种内联替换,仍应遵守7.1.2中定义的内联函数的其他规则。
C
14(n3797)中的命令行
在头文件中,它有一个有趣的副作用:内联函数可以在同一个模块中定义多次,链接器将简单地将“它们”连接成一个(如果它们由于编译器的原因而没有内联)。
对于在内部声明的静态变量,标准明确指出有一个,而且只有一个:
外部内联函数中的静态局部变量始终引用同一对象。
7.1.2/4 - C98/C14(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++的行为是正确的,而您误解了“内联”和“静态”的真实的含义。

eiee3dmh

eiee3dmh2#

我相信编译器会创建变量的多个副本,但链接器会选择一个,并让所有其他的变量引用它。当我尝试创建不同版本的内联函数时,我得到了类似的结果;如果函数实际上不是内联的(调试模式),所有调用都会转到同一个函数,而不管它们是从哪个源文件调用的。
就像编译器一样思考一下--它怎么可能不是这样呢?每个编译单元(源文件)都是独立于其他单元的,并且可以单独编译;因此每个单元都必须创建变量的副本,并认为它是唯一的一个。链接器有能力跨越这些边界,调整变量和函数的引用。

7uzetpgm

7uzetpgm3#

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

参见[dcl.fct.spec]/4

[..]带有外部链接的内联函数在所有翻译单元中必须具有相同的地址。外部内联函数中的静态局部变量总是引用相同的对象。外部内联函数中的字符串字面量在不同的翻译单元中是相同的对象。
我没有标准的副本来检查,但它与我在VS Express 2008中检查程序集的经验相匹配

6jjcrrmo

6jjcrrmo4#

应该是这样的。“static”告诉编译器你希望函数是编译单元的本地函数,因此你希望每个编译单元都有一个副本,每个函数示例都有一个静态变量的副本。
“inline”过去是告诉编译器你希望函数内联;现在,它只是把它当作“如果有几个代码副本也没关系,只要确保它是同一个函数”。
注:这个答案是回应原发帖者发给自己的答案而写的。

yfjy0ee7

yfjy0ee75#

自从我写了这个问题之后,我就在Visual Studio 2008中进行了尝试。我尝试打开所有使VS符合标准的选项,但可能漏掉了一些。以下是结果:
当函数仅仅是“内联”时,静态变量只有一个副本。
当函数为“静态内联”时,副本的数量与翻译单元的数量一样多。
现在真实的的问题是,事情是否应该是这样的,或者这是否是Microsoft C++编译器的一个理念。

2sbarzqh

2sbarzqh6#

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

eqzww0vc

eqzww0vc7#

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

t98cgbkg

t98cgbkg8#

我相信你最终会得到每个翻译单元一个。你实际上已经得到了这个函数的许多版本(及其声明的静态变量),每个包含头部的翻译单元一个。

628mspwn

628mspwn9#

除了所有这些可能暗示的设计问题之外,既然你已经被它卡住了,你应该在这种情况下使用静态而不是内联。这样每个人都共享相同的变量。(静态函数)

相关问题