C++ POD类型何时会被零初始化?

16

作为一个C语言背景下的开发者,我曾经认为POD类型(例如int)在C++中不会自动清零,但事实上这是错误的!

我的理解是只有未初始化的非静态POD值才不会被自动清零,就像代码片段所示。我理解得对吗?还有其他重要情况我是否遗漏了?

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是最后一个使用该物理内存页的进程,则可能存在登录信息或私钥。 - Mike DeSimone
当内存被分配时,操作系统不断地清零似乎有些浪费。 - Alan
2
你也可以阅读迈克尔·伯尔(Michael Burr)在回答“在使用new时,类型名称后面的括号有什么区别吗?”(Do the parentheses after the type name make a difference with new?)问题时所写的相关优秀帖子。 - James McNellis
你正在运行调试版还是发布版?使用的是哪个平台?哪个编译器? - JBRWilkinson
那篇文章太棒了...谢谢詹姆斯。 - Polaris878
5个回答

18
假设在调用test()之前您没有修改a,因为具有静态存储期的对象在程序启动时会进行零初始化,所以a的值为零。
d[0]的值为零,因为std::vector 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
谢谢。你在评论中提供的链接太棒了,再次感谢! - Roddy
2
如果POD类型对象没有初始化程序(例如Foo f;),则该对象将保持未初始化状态。Foo f = Foo();创建一个值初始化的Foo(这就是Foo()的作用),然后使用它来初始化f - James McNellis

1

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

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

  • a 应该未初始化。
  • b 应该未初始化。
  • c 应该指向一个新的(未初始化的)int。
  • d 应该被初始化为 [0](正如您正确猜测的那样)。

我认为在今天的大多数类Unix操作系统中,变量a会被清零为0。从技术上讲,变量a位于.bss段中,在调用main()函数之前通常会将其设置为所有0。 - Mike DeSimone
是的,但我的观点是即使代码中该值为零,他也不应该依赖于它。显式初始化才是正确的方法。 - utnapistim
1
除非在调用test()之前修改了变量a,否则它的值将为零。具有静态存储期的对象在程序启动时进行零初始化。 - James McNellis

1
请注意,操作系统作为安全功能所执行的零初始化通常只在第一次分配内存时执行。我的意思是堆、栈和数据段中的任何段。堆和数据段通常具有固定大小,并在应用程序加载到内存时进行初始化。
数据段(包含静态/全局数据和代码)通常不会被“重新使用”,尽管如果您在运行时动态加载代码,则可能不是这种情况。
堆栈段中的内存始终被重复使用。本地变量、函数堆栈帧等都在不断地使用和重复使用,并且不是每次都进行初始化 - 只有在应用程序首次加载时才进行初始化。
但是,当应用程序请求堆内存时,内存管理器通常会在授予请求之前对内存段进行零初始化,但仅适用于新段。如果您请求堆内存,并且已经初始化了一个段中的空闲空间,则不会再次执行初始化。因此,不能保证如果您的应用程序重新使用该特定内存段,它将再次获得零初始化。
例如,如果您在堆上分配一个 Foo,为其字段赋值,删除 Foo 实例,然后在堆上创建一个新的 Foo,那么新的 Foo 有可能被分配到与旧的 Foo 完全相同的内存位置,因此它的字段最初将具有与旧的 Foo 字段相同的值。
如果您仔细想一想,这是有道理的,因为操作系统只初始化数据以防止一个应用程序访问另一个应用程序的数据。允许应用程序访问自己的数据风险较小,因此出于性能原因,初始化不会每次都执行-仅在应用程序首次使用某个内存段时(在任何段中)执行。
有时,在调试模式下运行应用程序时,某些调试模式运行时会在每次分配时初始化堆栈和堆数据(因此您的 Foo 字段将始终被初始化)。但是,不同的调试运行时将数据初始化为不同的值。有些是零初始化,有些是初始化为“标记”值。
重要的是-永远不要在代码中任何地方使用未初始化的值。绝对没有保证它们将被零初始化。此外,请确保阅读先前链接的文章,了解 parens 和默认 vs 值初始化的区别,因为这会影响“未初始化”值的定义。

在 OP 的例子中,a 肯定会被初始化为零,这是绝对有保证的。 - Martin Bonner supports Monica

0

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


0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接