How can I declare a global const object in cpp so that its associated data is fully stored in .rodata instead of the object being created during runtime initialization and needlessly copied?
For example if I make globals of type
const std::string
const std::array<const std::string, 4>
const std::map<std::string, const std::string>
Tests show me these will compile into .bss and therefore requires runtime initialization despite it being constant data known at compile time ... and it needs to know WHAT to initialize them to, so it is also needlessly duplicating the data, using extra memory.
How do I get actual const objects that reside in .rodata without any runtime initialization?
As it is likely the C++ standard isn't specific enough for this, if you need some compiler specific feature, something supported by g++ and/or clang++ would be appreciated.
NOTE: Please, if your answer is something something boost or something something specific library, explain how the library accomplishes this. I want to understand how this is accomplished.
The following notes can be ignored, but included because the initial reaction I keep getting from people is "no way, a const string or const array doesn't need run time initialization or duplicate data".
So here is an example and some tests:
test.cpp
#include <iostream>
#include <fstream>
#include <string>
#include <array>
#include <map>
std::string str {"here"};
const std::string cstr {"there"};
std::array<std::string, 3> arr {"eight", "six", "seven"};
const std::array<const std::string, 4> carrc {"five", "nine", "oh", "three"};
const std::map<std::string, const std::string> cmapc = {
{"a", "apple"},
{"b", "bananna"},
{"c", "carrot"},
};
void show_info(const char *name, const void *a, const void *b)
{
std::cout << name << "\t" << a << " " << b << std::endl;
}
int main(int argc, char **argv) {
#define INFO(x) show_info(#x, &x, x.data())
INFO(str);
INFO(cstr);
INFO(arr);
INFO(arr[1]);
INFO(carrc);
INFO(carrc[1]);
std::cout << "cmapc" << "\t" << (void *)&cmapc << std::endl;
INFO(cmapc.at("a"));
std::ifstream infile("/proc/self/maps");
std::string line;
while(std::getline(infile, line)) {
std::cout << line << std::endl;
}
return 0;
}
compile and check where our objects were placed
$ g++ -std=c++17 -o test test.cpp
$ readelf -W -S test | grep -E "(.rodata|.data|.bss)"
[16] .rodata PROGBITS 0000000000005ad0 005ad0 0000e9 00 A 0 0 8
[24] .data PROGBITS 0000000000209000 009000 000018 00 WA 0 0 8
[25] .bss NOBITS 0000000000209020 009018 000290 00 WA 0 0 32
$ readelf -s test | grep OBJ | grep -E "[^_](str|arr|map)"
37: 00000000002091e0 32 OBJECT LOCAL DEFAULT 25 _ZL4cstr
38: 0000000000209200 128 OBJECT LOCAL DEFAULT 25 _ZL5carrc
39: 0000000000209280 48 OBJECT LOCAL DEFAULT 25 _ZL5cmapc
88: 0000000000209160 96 OBJECT GLOBAL DEFAULT 25 _Z3arrB5cxx11
105: 0000000000209140 32 OBJECT GLOBAL DEFAULT 25 _Z3strB5cxx11
You can also just run the program and see the output.
Or see the run-time initialization in gdb
$ gdb -q ./test
Reading symbols from ./test...(no debugging symbols found)...done.
(gdb) b _start
Breakpoint 1 at 0x2180
(gdb) r
Starting program: /tmp/test
Breakpoint 1, 0x0000000008002180 in _start ()
(gdb) x/4gx &str
0x8209140 <_Z3strB5cxx11>: 0x0000000000000000 0x0000000000000000
0x8209150 <_Z3strB5cxx11+16>: 0x0000000000000000 0x0000000000000000
(gdb) b main
Breakpoint 2 at 0x800230f
(gdb) c
Continuing.
Breakpoint 2, 0x000000000800230f in main ()
(gdb) x/4gx &str
0x8209140 <_Z3strB5cxx11>: 0x0000000008209150 0x0000000000000004
0x8209150 <_Z3strB5cxx11+16>: 0x0000000065726568 0x0000000000000000