Чрезмерно нетерпеливая инициализация нулями C++ union с constexpr

Ниже приведен сокращенный пример помеченного шаблона объединения "Хранение", который может предполагать два типа L и R, заключенные в объединение, плюс логическое значение, указывающее, какой из них сохраняется. При создании экземпляра используются два типа разного размера, причем меньший фактически пустой.

#include <utility>

struct Empty
{
};

struct Big
{
        long a;
        long b;
        long c;
};

template<typename L, typename R>
class Storage final
{
public:
        constexpr explicit Storage(const R& right) : payload{right}, isLeft{false}
        {
        }

private:
        union Payload
        {
                constexpr Payload(const R& right) : right{right}
                {
                }
                L left;
                R right;
        };

        Payload payload;
        bool isLeft;
};

// Toggle constexpr here
constexpr static Storage<Big, Empty> createStorage()
{
        return Storage<Big, Empty>{Empty{}};
}

Storage<Big, Empty> createStorage2()
{        
        return createStorage();
}
  • Конструктор инициализирует R-член с помощью Empty и вызывает только конструктор объединения для этого члена.
  • Объединение никогда не инициализируется по умолчанию в целом
  • Все конструкторы constexpr

Поэтому функция createStorage2 должна заполнять только тег bool и оставлять объединение в покое. Поэтому я ожидал бы результата компиляции с оптимизацией по умолчанию "-O":

createStorage2():
        mov     rax, rdi
        mov     BYTE PTR [rdi+24], 0
        ret

И GCC, и ICC вместо этого генерируют что-то вроде

createStorage2():
        mov     rax, rdi
        mov     QWORD PTR [rdi], 0
        mov     QWORD PTR [rdi+8], 0
        mov     QWORD PTR [rdi+16], 0
        mov     QWORD PTR [rdi+24], 0
        ret

обнуление всей 32-байтовой структуры, в то время как clang генерирует ожидаемый код. Вы можете воспроизвести это с помощью https://godbolt.org/z/VsDQUu. GCC вернется к желаемой инициализации тега bool только тогда, когда вы удалите constexpr из статической функции createStorage, в то время как ICC останется без впечатлений и все равно заполнит все 32 байта.

Это, вероятно, не является стандартным нарушением, поскольку неиспользуемые биты, являющиеся "неопределенными", позволяют все, в том числе обнуление и использование ненужных циклов процессора. Но досадно, если вы ввели профсоюз в первую очередь из соображений эффективности, а члены вашего профсоюза сильно различаются по размеру.

Что здесь происходит? Можно ли каким-либо образом обойти это поведение при условии, что удаление constexpr из конструкторов и статической функции не является вариантом?

Примечание: кажется, что ICC выполняет некоторые дополнительные операции, даже когда все constexpr удалены, как в https://godbolt.org/z/FnjoPC:

createStorage2():
        mov       rax, rdi                                      #44.16
        mov       BYTE PTR [-16+rsp], 0                         #39.9
        movups    xmm0, XMMWORD PTR [-40+rsp]                   #44.16
        movups    xmm1, XMMWORD PTR [-24+rsp]                   #44.16
        movups    XMMWORD PTR [rdi], xmm0                       #44.16
        movups    XMMWORD PTR [16+rdi], xmm1                    #44.16
        ret                                                     #44.16

Какова цель этих инструкций по перемещению?

1 ответ

(Это просто мои предположения, но это слишком долго для комментариев)

Что здесь происходит?

Поскольку конструкторы constexpr, может быть, Payloadв целом имеет некоторое значение, вычисленное во время компиляции. Затем во время выполнения этот полныйPayloadвозвращается. Насколько мне известно, компилятору не требуется распознавать, что определенная часть значения времени компиляции не инициализирована и что он не должен генерировать для нее код.

В каком-нибудь сумасшедшем компиляторе могло случиться так, что время компиляции Payload имеет значения мусора в неинициализированном разделе, и тогда он будет производить, например:

createStorage2():
        mov     rax, rdi
        mov     QWORD PTR [rdi], 0xbaadf00d
        mov     QWORD PTR [rdi+8], 0xbaadf00d
        mov     QWORD PTR [rdi+16], 0xbaadf00d
        mov     QWORD PTR [rdi+24], 0
        ret

В основном constexpr не любит неинициализированные значения, но союзы - это немного способ обойти это.

Другие вопросы по тегам