In the C++11 standard there is a note regarding the array backing the uniform initialisation that states:
The implementation is free to allocate the array in read-only memory if an explicit array with the same initializer could be so allocated.
Does GCC/Clang/VS take advantage of this? Or is every initialisation using this feature subject to additional data on the stack, and additional initialisation time for this hidden array?
For instance, given the following example:
void function()
{
std::vector<std::string> values = { "First", "Second" };
...
Would each of the compilers mentioned above store the backing array to the uniform initialisation in the same memory as a variable declared static const
? And would each of the compilers initialise the backing array when the function is called, or on application initialisation? (I'm not talking about the std::initializer_list<std::string>
that would be created, but rather the "hidden array" it refers to.
This is my attempt to answer my own question for at least GCC. My understanding of the assembler output of gcc is not fantastic, so please correct as necessary.
Using initializer_test.cpp
:
#include <vector>
int main()
{
std::vector<long> values = { 123456, 123457, 123458 };
return 0;
}
And compiling using gcc v4.6.3 using the following command line:
g++ -Wa,-adhln -g initializer_test.cpp -masm=intel -std=c++0x -fverbose-asm | c++filt | view -
I get the following output (cut down to the hopefully relevant bits):
5:initializer_test.cpp **** std::vector<long> values = { 123456, 123457, 123458 };
100 .loc 2 5 0
101 0009 488D45EF lea rax, [rbp-17] # tmp62,
102 000d 4889C7 mov rdi, rax #, tmp62
103 .cfi_offset 3, -24
104 0010 E8000000 call std::allocator<long>::allocator() #
104 00
105 0015 488D45D0 lea rax, [rbp-48] # tmp63,
106 0019 BA030000 mov edx, 3 #, <-- Parameter 3
106 00
107 001e BE000000 mov esi, OFFSET FLAT:._42 #, <-- Parameter 2
107 00
108 0023 4889C7 mov rdi, rax #, tmp63 <-- Parameter 1
109 0026 E8000000 call std::initializer_list<long>::initializer_list(long const*, unsigned long) #
109 00
110 002b 488D4DEF lea rcx, [rbp-17] # tmp64,
111 002f 488B75D0 mov rsi, QWORD PTR [rbp-48] # tmp65, D.10602
112 0033 488B55D8 mov rdx, QWORD PTR [rbp-40] # tmp66, D.10602
113 0037 488D45B0 lea rax, [rbp-80] # tmp67,
114 003b 4889C7 mov rdi, rax #, tmp67
115 .LEHB0:
116 003e E8000000 call std::vector<long, std::allocator<long> >::vector(std::initializer_list<long>, std::allocator<long> const&) #
116 00
117 .LEHE0:
118 .loc 2 5 0 is_stmt 0 discriminator 1
119 0043 488D45EF lea rax, [rbp-17] # tmp68,
120 0047 4889C7 mov rdi, rax #, tmp68
121 004a E8000000 call std::allocator<long>::~allocator() #
and
1678 .section .rodata
1679 0002 00000000 .align 16
1679 00000000
1679 00000000
1679 0000
1682 ._42:
1683 0010 40E20100 .quad 123456
1683 00000000
1684 0018 41E20100 .quad 123457
1684 00000000
1685 0020 42E20100 .quad 123458
1685 00000000
Now if I'm understanding the call on line 109 correctly in the context of x86-64 System V AMD64 ABI calling convention (the parameters I've annotated to the code listing), this is showing that the backing array is being stored in .rodata
, which I am taking to be the same memory as static const data. At least for gcc 4.6 anyway.
Performing a similar thing test but with optimisations turned on (-O2
) it seems the initializer_list
is optimised out:
70 .file 2 "/usr/include/c++/4.6/ext/new_allocator.h"
71 .loc 2 92 0
72 0004 BF180000 mov edi, 24 #,
72 00
73 0009 E8000000 call operator new(unsigned long) #
73 00
74 .LVL1:
75 .file 3 "/usr/include/c++/4.6/bits/stl_algobase.h"
76 .loc 3 366 0
77 000e 488B1500 mov rdx, QWORD PTR ._42[rip] # ._42, ._42
77 000000
90 .file 4 "/usr/include/c++/4.6/bits/stl_vector.h"
91 .loc 4 155 0
92 0015 4885C0 test rax, rax # D.11805
105 .loc 3 366 0
106 0018 488910 mov QWORD PTR [rax], rdx #* D.11805, ._42
107 001b 488B1500 mov rdx, QWORD PTR ._42[rip+8] # ._42, ._42
107 000000
108 0022 48895008 mov QWORD PTR [rax+8], rdx #, ._42
109 0026 488B1500 mov rdx, QWORD PTR ._42[rip+16] # ._42, ._42
109 000000
110 002d 48895010 mov QWORD PTR [rax+16], rdx #, ._42
124 .loc 4 155 0
125 0031 7408 je .L8 #,
126 .LVL3:
127 .LBB342:
128 .LBB343:
129 .loc 2 98 0
130 0033 4889C7 mov rdi, rax #, D.11805
131 0036 E8000000 call operator delete(void*) #
All in all, std::initializer_list
is looking pretty optimal in gcc.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With