我有一个在头文件中声明和定义的函数。这本身就是一个问题。如果未内联该函数,则使用该标头的每个翻译单元都将获得该函数的副本,并且当它们链接在一起时,它们将被复制。我通过使函数内联来“修复”该问题,但恐怕这是一个脆弱的解决方案,因为据我所知,即使指定了“ inline”关键字,编译器也不保证内联。如果不是这样,请纠正我。
无论如何,真正的问题是,此函数内的静态变量会发生什么?我最终得到多少份?
我有一个在头文件中声明和定义的函数。这本身就是一个问题。如果未内联该函数,则使用该标头的每个翻译单元都将获得该函数的副本,并且当它们链接在一起时,它们将被复制。我通过使函数内联来“修复”该问题,但恐怕这是一个脆弱的解决方案,因为据我所知,即使指定了“ inline”关键字,编译器也不保证内联。如果不是这样,请纠正我。
无论如何,真正的问题是,此函数内的静态变量会发生什么?我最终得到多少份?
Answers:
我想您在这里缺少什么。
声明一个函数静态将使其在其编译单元中“隐藏”。
具有名称空间范围(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 ++的行为是正确的,并且您误解了“内联”和“静态”的真实含义。
inline void doSomething() { static int value ; }
,该功能具有外部链接;如果它出现在包含在两个不同单元中的标头中,则表示这是ODR违规行为
inline
,它不能违反ODR。
我相信编译器会创建该变量的许多副本,但是链接器会选择一个副本并使所有其他引用它。当我尝试创建内联函数的不同版本时,我得到了相似的结果。如果未真正内联该函数(调试模式),则所有调用都转到同一个函数,而不管调用它们的源文件如何。
像编译器一样思考一下-否则会怎样?每个编译单元(源文件)彼此独立,并且可以分别编译。因此,每个人都必须创建变量的副本,并认为它是唯一的。链接器可以跨越这些边界并调整变量和函数的引用。
应该是这种方式。“静态”告诉编译器您希望函数在编译单元本地,因此,您希望每个编译单元一个副本,并且每个函数实例一个静态变量副本。
“内联”用于告诉编译器您希望内联函数;如今,它只是因为“如果有多个代码副本,只需确保它具有相同的功能就可以了”。因此,每个人都共享静态变量。
注意:此答案是针对原始张贴者自己张贴的答案而写的。
inline
导致函数被内联或者可以有多个副本?
inline
不会引起内联,它只是暗示了这一点,它允许多个定义(但不能在同一编译单元中)。
自从我写了这个问题以来,我在Visual Studio 2008中尝试了这个问题。我试图打开所有使VS符合标准的选项,但是有可能我错过了一些。结果如下:
当函数只是“内联”时,静态变量只有一个副本。
当该功能为“静态内联”时,副本数与翻译单元数相同。
现在真正的问题是,是否应该采用这种方式,或者这是否是Microsoft C ++编译器的意识形态。
内联表示将可执行代码(指令)内联到调用函数的代码中。不管您是否要求,编译器都可以选择执行此操作。这对函数中声明的变量(数据)没有影响。
我相信您最终将获得每个翻译单元一个。实际上,您已经获得了该函数的许多版本(及其声明的静态变量),每个包含标头的翻译单元都有一个版本。
静态意味着一个副本分布在整个程序中,但是内联意味着它在同一程序中多次需要相同的代码,因此无法在内联函数中使变量成为静态。