Различные инструкции, когда я использую прямую инициализацию vs std :: initializer_list - PullRequest
3 голосов
/ 17 марта 2020

Я играл с различными методами инициализации и оценивал опыт разработчика, который они предоставляют для различных вариантов использования и их влияния на производительность. В процессе я написал два фрагмента кода, один с прямой инициализацией и один со списком инициализаторов.

Direct:

class C {
public:
    char* x;

    C() : x(new char[10] {'0','1'}) {}
};

C c;

Список инициализаторов:

#include <utility>
#include <algorithm>

class C {
public:
    char* x;

    C(std::initializer_list<char> il) {
        x = new char[10];
        std::copy(il.begin(), il.end(), x);
    }
};

C c = {'0','1'};

Я ожидал, что std::initializer_list создаст ту же сборку, и хотя я могу понять, почему она может быть хуже (выглядит более сложной), я бы задал здесь совсем другой вопрос, если бы это было на самом деле хуже. Вообразите мое удивление, когда я увидел, что он генерирует лучше код с меньшим количеством инструкций!

Ссылка на Godbolt с помощью приведенных выше фрагментов - https://godbolt.org/z/i3XZ_K

Direct:

_GLOBAL__sub_I_c:
        sub     rsp, 8
        mov     edi, 10
        call    operator new[](unsigned long)
        mov     edx, 12592
        mov     WORD PTR [rax], dx
        mov     QWORD PTR [rax+2], 0
        mov     QWORD PTR c[rip], rax
        add     rsp, 8
        ret
c:
        .zero   8

Список инициализаторов:

_GLOBAL__sub_I_c:
        sub     rsp, 8
        mov     edi, 10
        call    operator new[](unsigned long)
        mov     edx, 12592
        mov     QWORD PTR c[rip], rax
        mov     WORD PTR [rax], dx
        add     rsp, 8
        ret
c:
        .zero   8

Сборка почти такая же, за исключением дополнительной инструкции mov QWORD PTR [rax+2], 0, которая на первый взгляд выглядит как завершение массива char с помощью нулевой символ. Однако в IIR C только строковые литералы (например, "01") автоматически завершаются нулевым символом, а не литералами массива символов.

Буду признателен за понимание того, почему сгенерированный код отличается, и что я могу сделать ( если возможно), чтобы сделать прямой случай лучше.

Кроме того, я нуб, когда дело доходит до сборки x86, поэтому дайте мне знать, если я упускаю что-то очевидное.

РЕДАКТИРОВАТЬ: Это только получает хуже, если я добавлю больше символов - https://godbolt.org/z/e8Gys_

1 Ответ

3 голосов
/ 17 марта 2020

Два примера фрагментов кода не имеют одинакового эффекта.

new char[10] {'0','1'}

Это агрегирует-инициализирует новый массив char, что означает, что все элементы массива, которым не присвоен инициализатор в заключенный в скобки список инициализаторов инициализируется в ноль.

x = new char[10];

Это не устанавливает ни один из элементов нового массива в любое значение, а

std::copy(il.begin(), il.end(), x);

только устанавливает элементы которые фактически указаны в std::initializer_list.

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

Нет способа частично инициализировать массив непосредственно в новом выражении. Но очевидное воспроизведение поведения второго примера будет выглядеть примерно так:

C() : x(new char[10]) {
    constexpr char t[]{'0','1'};
    std::copy(std::begin(t), std::end(t), x);
}

, хотя на самом деле вы не должны использовать new напрямую в любом случае. Вместо этого используйте std::vector<char>, std::array<char, 10>, std::string или std::unique_ptr<char[]> в зависимости от конкретного варианта использования. Все это позволяет избежать проблем со сроком службы, которые может вызвать new.

...