【问题标题】:When do C++ POD types get zero-initialized?C++ POD 类型何时进行零初始化?
【发布时间】:2010-06-23 13:36:07
【问题描述】:

来自 C 背景,我一直认为 POD 类型(例如 int)在 C++ 中从未自动零初始化,但似乎这是完全错误的!

我的理解是,只有“裸”的非静态 POD 值不会被零填充,如代码 sn-p 所示。我是否做对了,是否还有其他重要的案例我遗漏了?

static int a;

struct Foo { int a;};

void test()
{
  int b;     
  Foo f;
  int *c = new(int); 
  std::vector<int> d(1);

  // At this point...
  // a is zero
  // f.a is zero
  // *c is zero
  // d[0] is zero
  // ... BUT ... b is undefined     
}  

【问题讨论】:

  • 你确定它是 C++ 而不是你的操作系统吗?我想(但尚未检查)当操作系统为您的进程分配内存时,它会将其归零,至少在您触摸内存页面时。 C 和 C++ 都不需要这种行为,但如果操作系统将最后一个进程放入其中的内存页交给你,那将是一个很大的安全漏洞。如果 ssh 是使用该物理内存页面的最后一个进程,那么其中可能有登录信息或私钥。
  • 操作系统在分配内存时一直将其清零似乎有点浪费。
  • 您还可以阅读 Michael Burr 回复Do the parentheses after the type name make a difference with new?的优秀相关帖子
  • 您是在运行调试版本还是发布版本?哪个平台?哪个编译器?
  • 那个帖子太棒了......谢谢詹姆斯

标签: c++ constructor initialization


【解决方案1】:

假设您在调用test() 之前没有修改aa 的值为零,因为具有静态存储持续时间的对象在程序启动时被初始化为零。

d[0] 的值为零,因为std::vector&lt;int&gt; d(1) 调用的构造函数有第二个参数,它采用默认参数;第二个参数被复制到正在构造的向量的所有元素中。默认参数是T(),所以你的代码相当于:

std::vector<int> d(1, int());

b 有一个不确定的值是正确的。

f.a*c 都具有不确定的值。要对它们进行值初始化(对于 POD 类型与零初始化相同),您可以使用:

Foo f = Foo();      // You could also use Foo f((Foo()))
int* c = new int(); // Note the parentheses

【讨论】:

  • 大部分同意;只是我不确定为什么Foo f 不调用合成构造函数,如果调用了,为什么它与Foo f=Foo(); 有什么不同...
  • 谢谢。再次感谢您评论中的精彩链接!
  • @xtofl:如果 POD 类型对象不存在初始化程序(如 Foo f; 的情况),则该对象未初始化。 Foo f = Foo(); 创建一个值初始化的 Foo(这就是 Foo() 部分所做的),然后使用它来初始化 f
【解决方案2】:

实际上,某些值为零可能是由于您在应用程序的调试版本中尝试了此代码(如果是这种情况)。

如果我没记错的话,在你的代码中:

  • a 应该未初始化。
  • b 应该是未初始化的
  • c 应该指向一个新的(未初始化的)int
  • d 应该被初始化为 [0](你猜对了)

【讨论】:

  • 我认为在当今大多数类 Unix 操作系统中,a 将被清除为 0。从技术上讲,a 将位于 .bss 段中,通常在调用 main() 之前将其设置为全 0。
  • 是的,但我的意思是,即使该值在代码中显示为零,他也不应该依赖它。显式初始化是这里的方法。
  • 除非在调用test() 之前修改了a,否则它的值为零。具有静态存储时长的对象在程序启动时初始化为零。
【解决方案3】:

请注意,操作系统作为安全功能执行的零初始​​化通常仅在第一次分配内存时执行。我的意思是堆、堆栈和数据部分中的任何段。堆栈和数据部分通常具有固定大小,并在应用程序加载到内存时进行初始化。

数据段(包含静态/全局数据和代码)通常不会被“重用”,但如果您在运行时动态加载代码,情况可能并非如此。

堆栈段中的内存一直被重复使用。局部变量、函数堆栈框架等都在不断地使用和重复使用,并且不会每次都初始化 - 只是在首次加载应用程序时。

但是,当应用程序请求堆内存时,内存管理器通常会在授予请求之前将内存段初始化为零,但仅限于新段。如果您请求堆内存,并且已经初始化的段中有可用空间,则不会第二次进行初始化。因此,不能保证如果该特定内存段被您的应用程序重用,它将再次进行零初始化。

因此,例如,如果您在堆上分配一个 Foo,为其字段分配一个值,删除 Foo 实例,然后在堆上创建一个新的 Foo,那么新的 Foo 将有可能分配到与旧 Foo 的确切内存位置相同,因此它的字段最初将与旧 Foo 的字段具有相同的值。

如果您考虑一下,这是有道理的,因为操作系统只是在初始化数据以防止一个应用程序访问另一个应用程序的数据。允许应用程序访问其自己的数据的风​​险较小,因此出于性能原因,并非每次都进行初始化 - 只是第一次特定内存段可供应用程序使用(在任何段中)。

但是,有时当您在调试模式下运行应用程序时,某些调试模式运行时会在每次分配时初始化堆栈和堆数据(因此您的 Foo 字段将始终被初始化)。但是,不同的调试运行时会将数据初始化为不同的值。一些零初始化,一些初始化为“标记”值。

重点是 - 永远不要在代码中的任何地方使用未初始化的值。绝对不能保证它们将被零初始化。此外,请务必阅读之前链接的文章,了解括号和默认值与值初始化,因为这会影响“未初始化”值的定义。

【讨论】:

  • 这里 绝对 保证了 OP 示例中的 a 将被零初始化。
【解决方案4】:

对我来说,POD 类型的初始化取决于它们所在的内存部分。您的static int a 分配在数据段上,因此它在启动时具有默认值。但是,我认为 f 在您的示例中没有初始化...

【讨论】:

    【解决方案5】:

    他们没有。调试位版本可能会这样做,但通常它只是放入内存中,并初始化为内存中的任何值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-12-23
      • 1970-01-01
      • 2015-03-22
      • 2013-10-14
      • 2013-02-19
      • 1970-01-01
      • 1970-01-01
      • 2011-04-25
      相关资源
      最近更新 更多