Чрезмерно нетерпеливая инициализация нуля в C ++ с помощью constexpr - PullRequest
2 голосов
/ 06 марта 2020

Ниже приведен упрощенный пример помеченного шаблона объединения «Хранение», в котором могут быть два типа 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

И G CC, и I CC вместо этого генерируют что-то вроде

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. G CC вернется к желаемой инициализации тега bool, только когда вы удалите constexpr из функции "createStorage" stati c, в то время как I CC остается не впечатленным и по-прежнему заполняет все 32 байта.

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

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

Примечание: I CC, кажется, выполняет некоторые дополнительные операции, даже когда все 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 Ответ

0 голосов
/ 06 марта 2020

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

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

С тех пор конструкторы 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 не любит неинициализированные значения, но объединения являются способом обхода это немного.

...