【问题标题】:why c++ use memset(addr,0,sizeof(T)) to construct a object? Standard or compiler bug?为什么 c++ 使用 memset(addr,0,sizeof(T)) 构造对象?标准或编译器错误?
【发布时间】:2017-07-14 09:09:09
【问题描述】:

这个问题与我的另一个帖子有关:why allocate_shared and make_shared so slow

在这里我可以更清楚地描述问题。

想想下面的代码:

struct A {
    char data_[0x10000];
};

class C {
public:
    C() : a_() { }
    A a_;
};

int main() {
    C c;
    return 0;
}

我发现对于代码C() : a_(),编译器使用memset(addr,0,0x10000)作为A的构造函数。如果类型A有一个空的构造函数,那么asm代码是对的。

为了更清楚地描述问题,我写了一些测试代码:

#include <stdlib.h>

struct A {
    //A() {}
    char data_[0x10000];
    void dummy() { // avoid optimize erase by compiler
        data_[rand() % sizeof(data_)] = 1;
    }
    int dummy2() { // avoid optimize erase by compiler
        return data_[0];
    }
};

class B {
public:
    template<class ... T> B(T&...t) 
        : a_(std::forward<T>(t)...) {
    }
    A a_;
};

class C {
public:
    C() : a_() {
    }
    A a_;
};

template<class ... T>
int test(T&...t) {
    A a(t...);
    a.dummy();
    return a.dummy2();
}

int main() {
    A a;
    a.dummy();
    auto r1 = a.dummy2();

    auto r2 = test();

    B b;
    b.a_.dummy();
    auto r3 = b.a_.dummy2();

    C c;
    c.a_.dummy();
    auto r4 = c.a_.dummy2();
    return r1 + r2 + r3 + r4;
}

我用 vs2017 编译了代码,在 windows 10 中,x86 发布版本。 然后我检查了asm代码:

template<class ... T>
int test(T&...t) {
00E510B8  call        _chkstk (0E51CE0h)  
00E510BD  mov         eax,dword ptr [__security_cookie (0E53004h)]  
00E510C2  xor         eax,ebp  
00E510C4  mov         dword ptr [ebp-4],eax  
    A a(t...);
00E510C7  push        10000h  
00E510CC  lea         eax,[a]  
00E510D2  push        0  
00E510D4  push        eax  
00E510D5  call        _memset (0E51C3Ah)  
00E510DA  add         esp,0Ch  
    a.dummy();
00E510DD  call        dword ptr [__imp__rand (0E520B4h)]  
}
00E510E3  mov         ecx,dword ptr [ebp-4]  

很明显,函数test()调用memset(p, 0, 0x10000)

如果我在 A 中添加一个空的构造函数(A(){} 行),编译器会删除 memset。

那为什么A类型没有构造函数时代码调用memset,而A有构造函数时不调用memset?

它是 c++ 标准的一部分,还是只是编译器错误?

显然 memset(p, 0, sizeof(T)) 是无用且有害的,它会减慢程序的速度。我该如何解决?

【问题讨论】:

  • 这看起来像是默认初始化和零初始化之间的区别。不过,我对细节很模糊,所以请参阅例如stackoverflow.com/questions/27484483/…
  • 没有test2()函数...
  • @K.Kirsz 测试,抱歉

标签: c++ c++11 constructor perfect-forwarding


【解决方案1】:
A a(t...);

会被解析为用t...初始化at...为空时,如你调用它时,这将被理解为值初始化 em>a.

对于没有用户提供的默认构造函数的 Avalue-initialize 会将其所有成员归零,因此 memset

当您为A 提供构造函数时,value-initialize 是调用默认构造函数,您将其定义为不执行任何操作,因此不会调用 memset

这不是编译器中的错误,这是必需的行为。要删除多余的memset,你可以写A a;。在这种情况下,a默认初始化的,并且无论是否使用用户提供的构造函数,都不会发生自动归零。

†​​ 这很重要,因为A a() 将被解析为一个名为a 的函数,返回类型为A

【讨论】:

  • 看看B类,它是标准的完美转发。如果类 A 没有构造函数,则 B 也调用 memset。如何解决它?而且,请移到 url:stackoverflow.com/questions/45072486/…,我发现 std::allocate_shared 也做了这个无用的 memset,我不知道如何解决它。
  • @alpha 移动到网址是什么意思。如果问题是重复的,那么你做错了。如果不是,不要在一篇文章中提出多个问题
  • @alpha B 在其构造函数中调用a_(),这在为什么需要memset 的答案中直接涵盖。
  • 我首先遇到了那个url描述的性能问题,然后我挖了几天,最后发现是因为memset。这不是重复的问题。即使我理解了值初始化,我仍然不知道如何解决这个问题。
  • 如果我将 A 的定义更改为 struct A { A()=default;字符数据_[0x10000]; };我发现编译器仍然调用 memset。为什么?编译器应该为 A 生成一个虚拟构造函数?
【解决方案2】:

this不解释吗?

我们可以看到:

零初始化被执行 [...] 作为值初始化的一部分 value-initialized 类类型的 [...] 成员的序列 没有构造函数,包括元素的值初始化 没有提供初始化器的聚合。

...

值初始化在非静态数据成员时执行 [...] 或者基类使用成员初始化器初始化 空的一对圆括号或大括号 (C++11 起);

因此将a_() 放入成员初始化器列表属于后一种情况,因此会调用数组的零初始化。

回答您的问题:对我来说,这似乎是一种标准行为,而不是编译器错误。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-03-21
    • 2016-12-23
    • 1970-01-01
    • 1970-01-01
    • 2013-10-19
    • 2013-12-10
    • 1970-01-01
    • 2014-08-31
    相关资源
    最近更新 更多