Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Storage of the "hidden array" behind initializer_list

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.

like image 833
Shane Avatar asked Aug 29 '12 15:08

Shane


1 Answers

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.

like image 121
Shane Avatar answered Nov 15 '22 11:11

Shane