C++11 VS2013类POD成员初始化

Posted

技术标签:

【中文标题】C++11 VS2013类POD成员初始化【英文标题】:C++11 VS2013 class POD member initialization 【发布时间】:2014-07-29 23:21:02 【问题描述】:

我到处寻找对此的解释,但我做不到。我从 VS2013 v120 平台工具集中看到了这种行为,但是当我将工具集设置为 v90(VS2008 工具集)时,一切都未初始化。我相信这是由于 C++11 的一些变化,但也可能是 v120 平台工具集的异常。

谁能解释一下 C++/C++11 级别的情况?也就是说,为什么b 被清零了?为什么j 也没有归零? (即为什么结构与类的行为不同)

另外,我知道我输出数据的方式是未定义的行为,请忽略它。以这种方式在此处发布比调试器窗口更容易。这是在 32 位上运行的,因此指针的大小与 unsigned int 相同。

考虑以下代码:

#include <iostream>

class Foo 
public:
  int a,
      *p;
;

class Bar 
public:
  Bar()
  int a,
      *p;
;

struct Jar 
  Jar()
  int a,
      *p;
;

int main() 
  Foo f;
  Bar b;
  Jar j;
  std::cout << std::hex; // please excuse this undefined-behavior producing test code, it's more simple to show this than a debugger window on SO (this is on 32-bit)
  std::cout << "f: " << ((unsigned*)&f)[0] << ' ' << ((unsigned*)&f)[1] << std::endl;
  std::cout << "b: " << ((unsigned*)&b)[0] << ' ' << ((unsigned*)&b)[1] << std::endl;
  std::cout << "j: " << ((unsigned*)&j)[0] << ' ' << ((unsigned*)&j)[1] << std::endl;
  return 0;

这是输出:

f: cccccccc cccccccc  
b: 0 0  
j: cccccccc cccccccc

编辑: 这是我看到的与Bar b; 相关的反汇编 __autoclassinit2 正在将内存归零。它不是构造函数的一部分,而是在构造函数调用之前归零。

  Bar b;
00886598  push        8  
0088659A  lea         ecx,[b]  
0088659D  call        Bar::__autoclassinit2 (0881181h)  
008865A2  lea         ecx,[b]  
008865A5  call        Bar::Bar (0881109h)  

【问题讨论】:

你问为什么一些未初始化的变量有一个值而其他的有不同的值?那是因为未初始化的变量具有不确定的值。正如您所指出的,使用这些值会产生未定义的行为。因此,您问题的唯一答案是“因为任何事情都可能发生”。 @Apriori:在所有三种情况下,所有成员都包含垃圾值。这里没有什么“归零”。如果您在输出中看到零,它们纯粹是偶然的,任何其他值也是如此。 IE。您的零是垃圾零,而不是确定性零。 @Mike Seymour:我说的是输出类型中的未定义行为,双关语值并将它们打印出来作为示例。但是在我的场景中(在我面前的机器上)我在调试器中查看它们,这就是我所看到的。当然,在调试器中查看变量不会触发未定义的行为。 @Apriori:很公平。您在调试器中看到的是未初始化内存的内容,其中可能包含任何内容。 然后,我的 Mac 上的 clang 3.4 提供了,f: 1f30 20 b: 1f34 20 j: 1f38 20 在 32 位发布模式下,f: 300 700 b: f00 1300 j: 1c00 2100 在 64 位发布模式下。 【参考方案1】:

您的所有类型都包含内置类型的数据成员,因此除非您执行以下操作之一(以Foo 为例),否则它们都不会被零初始化:

初始化默认构造函数中的成员:

class Foo 
public:
  Foo() : a(), p() 
  int a,
      *p;
;

或非静态数据成员初始化器(brace-or-equal-initializer

class Foo 
public:
  int a = 0,
      *p = nullptr;
;

或者保持Foo不变,并且值初始化实例

Foo f;

使用原始示例,我无法重现您使用 VS2013、32 位调试版本观察到的结果。我得到的输出是

f: cccccccc cccccccc
b: cccccccc cccccccc
j: cccccccc cccccccc

编辑:我能够重现 b 被零初始化的行为。如果您启用 /sdl安全开发生命周期检查)编译器开关(在配置属性 -> C/C++ -> 常规下),则会发生这种情况。

来自MSDN documentation 的开关:

启用/sdl 时,编译器会生成代码以在运行时执行这些检查: — ... — 执行类成员初始化。 在对象实例化时(在构造函数运行之前)自动将所有类成员初始化为零。这有助于防止使用与构造函数未显式初始化的类成员关联的未初始化数据。

这个blog post 甚至提到了__autoclassinit 函数,尽管他列出的启发式方法与我们观察到的不完全匹配,因为此功能has changed between VS2012 和VS2013 的行为。

同样一文不值的是,编译器似乎不仅区分聚合(Foo)和非聚合(另外两个),这是有道理的,但由于某些真正奇怪的原因,它只会执行此操作如果您在类定义中使用 class-key class 而不是 struct,则零初始化。

【讨论】:

+1 值得注意的是,第三个选项的行为明显不同,具体取决于上述类是否提供了用户定义的默认构造函数,而 not 提供了正确的初始化列表(例如如BarJar)。 您的平台工具集是否设置为 v120?我不确定是否有启用/禁用 C++11 语言功能的显式选项,但如果有,请确保它已关闭。我在默认配置下运行,这是一个新项目,只是为了测试它。 值得注意的是,如果我删除构造函数并添加一个虚拟方法,我也会在 Bar 对象中看到零。 @Apriori 是的,工具集 v120(这是我安装的唯一版本)。而且我不知道在 MSVC 中启用/禁用 C++11 模式的任何选项。通过添加virtual 函数,您添加了一个指向该类的 vtable 指针,AFAIK、VC 将其作为第一个成员,因此在这种情况下您真的不应该看到任何零。我得到b: f1dae8 cccccccc @Praetorian:我的意思是,当存在虚函数时,我在调试器中看到成员的零,而不是在输出中。我认为使用调试器屏幕截图而不是控制台输出可能会更好地提出这个问题,但是在讨论过之后我现在犹豫要不要更改它。我现在在第二台装有 VS2013 的机器上试过这个,我仍然看到 b 的零。但是,我也尝试过放置 new(类名后没有括号),我猜想也会在 b 中放置零,但事实并非如此。

以上是关于C++11 VS2013类POD成员初始化的主要内容,如果未能解决你的问题,请参考以下文章

C++11新特性精讲(多线程除外)

我可以防止非 POD 类中数组数据成员中元素的零初始化吗?

VS 2013 的类内成员初始化程序失败

C ++ 11成员类初始化顺序[重复]

C++ Struct 未通过 POD 测试

具有抽象类的 C++11 对象组合