In a prior post, I talked about list initialization
, which is differs from initializer_list
discussed here. Though personally, I don’t find initializer_list
really useful as I never used it in my projects.
list initialization
is a general syntax using{}
for initializing a variety of variables and objects.initializer_list
is a template class representing a lightweight, read-only array of elements, typically used in constructors or functions.
initializer_list
promotes safety, flexibility and modern tone compared to the raw array. The example below shows the usage of initializer_list
.
Code
#include <initializer_list>
void Receiver(std::initializer_list<int> list); //the code cannot be correctly executed due to the missing definition
void Fun() {
std::initializer_list<int> list{3, 4, 5, 6};
Receiver(list); // Pass the initializer list
}
int main () {
Fun();
return 0;
}
With C++ Insights, we can see the compiler generates the code below. The initializer_list
turns out to be a view-like wrapper around a pointer
to a backing array
and the size
of the array.
#include <initializer_list>
void Receiver(std::initializer_list<int> list);
void Fun()
{
const int __list8_36[4] = {3, 4, 5, 6}; // create an array
std::initializer_list<int> list = std::initializer_list<int>{__list8_36, 4}; // a pointer to the array and the size
Receiver(std::initializer_list<int>(list));
}
int main()
{
Fun();
return 0;
}
We further investigate the code with the generated assembly code. With g++13
, the compiler generates the code below:
Fun():
sub rsp, 24 // Stack Allocation
movdqa xmm0, XMMWORD PTR .LC0[rip] // Load the constant pool into xmm0
mov esi, 4
mov rdi, rsp
movaps XMMWORD PTR [rsp], xmm0 // Copy the data from xmm0 to the stack
call Receiver(std::initializer_list<int>)
add rsp, 24
ret
main:
sub rsp, 24
movdqa xmm0, XMMWORD PTR .LC0[rip]
mov esi, 4 // set as the array size 4
mov rdi, rsp // rdi is set to the stack pointer (address of the copied array)
movaps XMMWORD PTR [rsp], xmm0
call Receiver(std::initializer_list<int>)
xor eax, eax
add rsp, 24
ret
.LC0: //Constant Pool
.long 3
.long 4
.long 5
.long 6
g++14
compiler optimizes the initializer_list
by generating a static
backing array:
Fun():
mov edi, OFFSET FLAT:C.0.0 // edi is loaded with the address of the static array
mov esi, 4 // set as the array size 4
jmp Receiver(std::initializer_list<int>)
main:
sub rsp, 8
mov edi, OFFSET FLAT:C.0.0
mov esi, 4
call Receiver(std::initializer_list<int>)
xor eax, eax
add rsp, 8
ret
C.0.0: // Static Data Section
.long 3
.long 4
.long 5
.long 6
Aspect | ASM with g++14 | ASM with g++13 |
---|---|---|
Memory Allocation | Backing array stored in the static data section (C.0.0) | Backing array copied onto the stack during function execution |
Lifetime | Persistent for the lifetime of the program | Temporary: exists only during the function’s execution |
Performance | More efficient: no copying required | Less efficient: incurs copying overhead to stack memory |
Stack Usage | No stack memory used for the array | Consumes stack space (24 bytes in this case) |
Instruction Set | Simple memory reference and register loads | Involves SIMD instructions (e.g., movdqa) and stack operations |
Reusability | Data can be reused without reallocation | Backing array is unique to each function call |
The optimization of g++14
on the initializer_list
is actually introduced by C++26, and backported to lower standard version (e.g., C++17, C++20) with compatible compiler. Though this optimization is a bit trivial, learning the benefit of static
is still useful.