默认构造函数是否会对成员数组变量进行零初始化?

4
当我查看以下程序及其输出时,我发现获取FrameA对象的返回值相当令人困惑:
  1. when a empty ctor is defined, the member array field is remain uninitialized
  2. when let the compiler generate the ctor, the member array field is initialized to all 0

    auto a = f();       // f() --> return A();
    
给定以下 SSCCE
#include <cstring>
#include <iostream>
#include <chrono>
#include <algorithm>

using namespace std;

const int MAX = 9999999;

struct FrameA {
  // FrameA() {}
  // FrameA(const FrameA &v) { memcpy(data, v.data, sizeof(data)); }
  char data[1000];
};

FrameA f(int i) { return FrameA(); }

int test(int odd) {
  int sum = 0;
  auto begin = chrono::steady_clock::now();
  for (int i = 0; i < MAX; ++i) {
    auto v = f(odd);
    sum += v.data[0] + v.data[330];
  }
  auto end = chrono::steady_clock::now();
  cout << chrono::duration_cast<chrono::milliseconds>(end - begin).count()
       << " (milliseconds)" << endl;
  return sum;
}

int _tmain(int argc, _TCHAR *argv[]) {
  test(0);
  test(1);
  return 0;
}

当定义了一个空的构造函数时,输出如下:

g++ v4.8.1

72 (毫秒)
73 (毫秒)

但是使用编译器生成的构造函数时,输出结果为:

g++ v4.8.1

1401 (毫秒)
1403 (毫秒)

我也在VC12上进行了测试,结果类似。
在检查汇编代码后,我发现当使用编译器生成的构造函数时:
  for (int i = 0; i < MAX; ++i) {
    auto v = f(odd);
00A31701  push        3E8h  
00A31706  lea         eax,[ebp-3F8h]  
00A3170C  push        0  
00A3170E  push        eax  
00A3170F  call        _memset (0A32460h)               ;; memset FrameA to 0
    sum += v.data[0] + v.data[330];
00A31714  movsx       eax,byte ptr [ebp-3F8h] 

但是使用空构造函数不会调用memset来将FrameA数组设置为零。

有什么解释吗?

顺便说一下,我搜索了C++ 11草案n3242,但第8.5章的zero-initializedefault-initialize似乎没有涵盖这种情况。我错过了什么吗?


编译器生成的构造函数不应该使用memset清空数组,我相信这是一个扩展。 - Sebastian Hoffmann
为了获得数组成员的值初始化,您可以使用 FrameA():data{} {} - dyp
3个回答

3

FrameA()将对对象进行值初始化(§5.2.3/2):

表达式T(),其中T是非数组完整对象类型的简单类型说明符类型名说明符,或者是(可能带有cv限定的)void类型,创建指定类型的prvalue,该prvalue被值初始化。

对于没有用户提供构造函数的非联合类类型,将进行零初始化(§8.5/7):

如果T是一个(可能带有cv限定的)没有用户提供构造函数的非联合类类型,则对象将进行零初始化,并且如果T的隐式声明默认构造函数是非平凡的,则调用该构造函数。

这会对其每个成员进行零初始化。

对于具有用户提供构造函数的类类型,将仅调用构造函数(在您的情况下,不会初始化数组)(§8.5/7):

如果T是一个(可能带有cv限定的)类类型(Clause 9),并且具有用户提供的构造函数(12.1),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化是非法的)。


3
当使用默认构造函数构造类型为T的对象,即使用T()时,根据T的定义方式,你将得到值初始化默认构造
  1. 如果T没有默认构造函数或者有默认的默认构造函数,则编译器会处理初始化:编译器对所有成员进行值初始化。对于内置类型,值初始化意味着它们被零初始化,即它们的值接收相应的适当的零表示。
  2. 如果T具有非默认的默认构造函数,则T的程序员负责初始化成员。成员可以列在成员初始化列表中并相应地初始化,或者它们可以进行默认初始化。对于内置类型的默认初始化意味着什么都不会发生,即这些成员未初始化。

2
如果T没有默认构造函数,且构造函数没有默认值,则如何处理如果它有一个非默认设置的默认构造函数? - dyp
@DyP:应用布尔逻辑并移动了否定!;-) 谢谢! - Dietmar Kühl

1

A() 使用了 值初始化。正如您所注意到的,这取决于 A 是否有用户声明的默认构造函数,其行为会有所不同。

  • 如果有,默认构造函数将被调用。任何未被构造函数明确初始化的成员将保持未初始化状态。对于像这样的自动或临时对象,这意味着它们不会被触及,并且将包含在内存中发生的任何垃圾。
  • 如果没有,默认情况下每个成员将进行值初始化。对于大多数基本类型,值初始化将它们设置为零。

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