Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Different instructions when I use direct initialization vs std::initializer_list

I was playing around with various methods of initialization and evaluating the developer experience they provide for various use cases and their performance impacts. In the process, I wrote two snippets of code, one with direct initialization and one with an initializer list.

Direct:

class C {
public:
    char* x;

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

C c;

Initializer list:

#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'};

I was expecting std::initializer_list to generate the same assembly and while I can understand why it might be worse (looks more complex), I would have been asking a very different question here if it was actually worse. Imagine my surprise when I saw that it generates better code with fewer instructions!

Link to godbolt with the above snippets - 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

Initializer list:

_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

The assembly is pretty much the same except for the extra mov QWORD PTR [rax+2], 0 instruction which on first glance seems like termination of the char array with a null character. However, IIRC only string literals (like "01") are automatically terminated with null char, not char array literals.

Would appreciate any insight into why the generated code is different and anything I can do (if possible) to make the direct case better.

Also, am a noob when it comes to x86 assembly, so let me know if I am missing something obvious.

EDIT: It only gets worse if I add more chars - https://godbolt.org/z/e8Gys_

like image 511
Roshan Avatar asked Dec 07 '25 12:12

Roshan


1 Answers

The two example code snippets do not have the same effect.

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

This aggregate-initializes the new char array, which means that all element of the array which are not given an initializer in the brace-enclosed initializer-list are initialized to zero.

x = new char[10];

This does not set any of the elements of the new array to any value and

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

only sets the elements that are actually specified in the std::initializer_list.

Therefore in the first version the compiler has to guarantee that all elements without specified initializer are set to zero, while it can just leave these values untouched in the second version.

There is no way to partially initialize an array directly in a new expression. But the obvious reproduction of the behavior of the second example would be something like

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

although, really, you shouldn't use new directly anyway. Instead use std::vector<char>, std::array<char, 10>, std::string or std::unique_ptr<char[]> depending on the concrete use case. All of these avoid lifetime issues that new would cause.

like image 157
walnut Avatar answered Dec 09 '25 02:12

walnut