2010-06-23 74 views
14

从C背景来看,我一直认为POD类型(例如ints)从来不会在C++中自动初始化,但似乎这显然是错误的!C++ POD类型何时获得零初始化?

我的理解是,只有'裸'的非静态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  
} 
+0

你确定它是C++而不是你的操作系统吗?我会想象(但没有检查过),当一个操作系统为你的进程分配内存时,至少当你触及内存页面时,它会将其归零。 C和C++都不需要这种行为,但是如果操作系统用最后一个进程放入其中的任何一个内存页面,这将是一个很大的安全漏洞。如果ssh是使用该物理内存页面的最后一个进程,那么可能存在登录信息或私钥。 – 2010-06-23 13:45:27

+0

操作系统在分配内存时要保持内存清零似乎有点浪费。 – Alan 2010-06-23 13:46:41

+2

您可能还会阅读Michael Burr在回复[[类型名称与新的区别之后的括号是否相同]]的优秀相关文章(http://stackoverflow.com/questions/620137/do-the-parentheses-在类型名称make-a-difference-with-new/620402#620402) – 2010-06-23 13:46:43

回答

16

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

d[0]的值为零,因为由std::vector<int> 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 
+0

大部分同意;只有我不确定为什么'Foo f'不调用合成的构造函数,如果是这样,为什么这与'Foo f = Foo();'... – xtofl 2010-06-23 13:57:15

+0

谢谢。并感谢您的评论中的真棒链接! – Roddy 2010-06-23 13:57:56

+1

@xtofl:如果对于POD类型的对象没有初始化方法(就像'Foo f;'的情况那样),那么该对象将保持未初始化状态。 'Foo f = Foo();'创建一个初始化值为'Foo'的值(这就是'Foo()'所做的),然后用它来初始化'f'。 – 2010-06-23 14:00:56

1

其实有些是零值的可能是因为你想在应用程序的调试版本的代码(如果是这样的话)。

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

  • 一个应该初始化。
  • 乙方应未初始化
  • C必须指向新的(未初始化)诠释
  • d应该被初始化为[0](如你猜中)
+0

我觉得今天大多数类Unix操作系统中'a'会被清除为'0'。从技术上讲,'a'会出现在'.bss'段中,在'main()'被调用之前通常被设置为全0。 – 2010-06-23 13:47:24

+0

是的,但我的观点是,即使代码中的值显示为零,他也不应该依赖该值。显式初始化是这里的方法。 – utnapistim 2010-06-23 13:51:33

+1

除非'a'在调用'test()'之前被修改,否则它的值为零。具有静态存储持续时间的对象在程序启动时被初始化为零。 – 2010-06-23 13:55:55

0

对于我来说,POD类型取决于它们被放置在内存的一部分初始化。您的static int a分配给数据段,因此它在启动时具有默认值。然而,我认为f在你的例子中没有inizialized ...

0

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

1

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

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

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

但是,当应用程序发出对堆内存的请求时,内存管理器通常会在授予请求之前对内存段进行零初始化,但仅限于新段。如果您请求堆内存,并且已经初始化的段中有可用空间,则不会再次进行初始化。因此,不能保证如果你的应用程序重用了这段特定的内存,它将再次被初始化为零。因此,例如,如果您在堆上分配Foo,为其字段赋值,删除Foo实例,然后在堆上创建新的Foo,则有可能分配新的Foo与旧的Foo具有相同的内存位置,所以它的字段最初将具有与旧的Foo字段相同的值。

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

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

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

+0

*绝对*是保证在OP的例子中'a'将被初始化为零。 – 2017-12-21 18:12:06