可能放置“新”依赖于底层存储价值?

时间:2017-01-27 09:29:04

标签: c++ optimization language-lawyer

让我们从一些背景开始。

自定义内存池使用的代码类似于以下代码:

struct FastInitialization {};

template <typename T>
T* create() {
    static FastInitialization const F = {};

    void* ptr = malloc(sizeof(T));
    memset(ptr, 0, sizeof(T));
    new (ptr) T(F);
    return reinterpret_cast<T*>(ptr);
}

这个想法是,当使用FastInitialization调用时,构造函数可以假设存储已经零初始化,因此只初始化那些需要不同值的成员。

然而,GCC(至少6.2和6.3)有一个“有趣”的优化。

struct Memset {
    Memset(FastInitialization) { memset(this, 0, sizeof(Memset)); }

    double mDouble;
    unsigned mUnsigned;
};

Memset* make_memset() {
    return create<Memset>();
}

编译为:

make_memset():
        sub     rsp, 8
        mov     edi, 16
        call    malloc
        mov     QWORD PTR [rax], 0
        mov     QWORD PTR [rax+8], 0
        add     rsp, 8
        ret

可是:

struct DerivedMemset: Memset {
    DerivedMemset(FastInitialization f): Memset(f) {}

    double mOther;
    double mYam;
};

DerivedMemset* make_derived_memset() {
    return create<DerivedMemset>();
}

编译为:

make_derived_memset():
        sub     rsp, 8
        mov     edi, 32
        call    malloc
        mov     QWORD PTR [rax], 0
        mov     QWORD PTR [rax+8], 0
        add     rsp, 8
        ret

也就是说,只有struct的前16个字节(与其基数对应的部分)已经初始化。调试信息确认已完全取消对memset(ptr, 0, sizeof(T));的调用。

另一方面,ICC和Clang都在全尺寸上调用memset,这是Clang的结果:

make_derived_memset():               # @make_derived_memset()
        push    rax
        mov     edi, 32
        call    malloc
        xorps   xmm0, xmm0
        movups  xmmword ptr [rax + 16], xmm0
        movups  xmmword ptr [rax], xmm0
        pop     rcx
        ret

因此GCC和Clang的行为有所不同,问题就变成了:GCC是否正确并且产生了更好的装配,或者是Clang是否正确以及GCC错误?

或者,就语言律师而言:

构造函数在哪种情况下可以依赖存储在其分配的存储中的先前值?

注意:我认为这只适用于展示位置new,但我很高兴以其他方式展示。

2 个答案:

答案 0 :(得分:11)

  

五月展示位置new是否依赖底层存储空间值?

不,可能不会。来自[dcl.init]:

  

如果没有为对象指定初始值设定项,则默认初始化该对象。存储对象时   如果获得自动或动态存储持续时间,则该对象具有不确定的值,如果未对该对象执行初始化,则该对象将保留不确定的值,直到该值被替换为止(5.18)。

不确定价值意味着不确定。这并不意味着,在放置新表达式的情况下,必须保持先前的存储器。允许编译器对内存执行任何操作 - 包括但不限于任何内容。

  

在哪种情况下构造函数可能依赖于存储在其分配的存储中的先前值?

[dcl.init]中的后续段落列出了在生成不确定值时行为未定义的情况,但它们只与无符号窄字符类型有关。

所以,在任何情况下都没有。

答案 1 :(得分:1)

GCC和Clang都是正确的 - 保留DerivedMemset未经初始化的(自己的)数据成员将在访问其值时立即导致未定义的行为。因此,在调用构造函数之前,编译器将获得离开的许可 - 存储范围内将被这些字段占用的任何位模式。