0

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
  • 1
    `std::string` allocates memory for the actual characters on the heap. I don't believe there's a way to avoid dynamic initialization for it. – Igor Tandetnik Aug 31 '20 at 01:00
  • Same thing with `std::map`, how can it create its tree at compile-time? – Some programmer dude Aug 31 '20 at 01:04
  • 1
    Which standard are you compiling your code against? The constructor for `std::string` that takes a `const char *` was made `constexpr` in C++20. I haven't tested it, but that suggests to me that short strings might be constructed at compile time. – JaMiT Aug 31 '20 at 01:05
  • @IgorTandetnik look at my example, for a global const std::string it does not go on the heap. It is in .bss – StarCrunch Aug 31 '20 at 01:49
  • @Someprogrammerdude it knows all the data at compile time, so it at least seems feasible. – StarCrunch Aug 31 '20 at 01:50
  • @JaMiT thank you for the suggestion. I installed clang++-10, and compiling with -std=c++17 and -std=c++20 made no difference. I'm not sure how to use constexpr, but just changing from `const std::string str {"there"}` to `constexpr std::string str {"there"}` gave compile warnings. That's probably not what you meant, but I'm not sure how to appropriately use constexpr here. – StarCrunch Aug 31 '20 at 02:16
  • When you have `std::string str {"here"};`, two objects are involved. There's a string literal `"here"` that probably goes into `.rodata`. There's the `std::string` object, that probably goes into `.bss`. Finally, at run time, during program initialization, the string's constructor is called with the pointer to the string literal as a parameter; this constructor allocates memory on the heap (barring small string optimization) and copies the contents of the string literal into that memory. – Igor Tandetnik Aug 31 '20 at 03:15
  • @IgorTandetnik In the example at least, it all ended up in bss. But if I'm understanding you correctly, you are saying that if the string is large enough, even though it is const and so the size is known at compile time, it will runtime initialize into the heap instead of bss? That is interesting. I'll have to try that out when I get home tonight. – StarCrunch Aug 31 '20 at 16:51

1 Answers1

0

You can define objects at compile-time by defining a constructor that supports it. Note that the members of the object should also be const.

In the following example, we create an object foo at compile time. We can validate this with the static_assert function. On the other hand, foo2 is created at runtime. Unfortunately, there is no panacea for your problem. Using constexpr can't assure that you will always end up with compile-time initialization as this will only happen when possible and the compiler won't complain if this doesn't happen.

struct Foo {
  const int elem1;
  const char elem2;
  constexpr Foo(int a, char b);
};

constexpr Foo::Foo(int a, char b) :
  elem1(a), elem2(b)
  {
  }

constexpr Foo foo(1, 'a');

static_assert(foo.elem1 == 1);
static_assert(foo.elem2 == 'a');

int main()
{
  Foo foo2(2, 'b');
}
emegona
  • 168
  • 1
  • 12
  • Thank you, I tried this with clang++-10 and it successfully put foo in .rodata – StarCrunch Aug 31 '20 at 02:29
  • I couldn't figure out how to change elem2 to a std::string as an example, but following https://stackoverflow.com/questions/27123306/is-it-possible-to-use-stdstring-in-a-constexpr I can make it a std::string_view and it all goes into .rodata So that solves strings. Is there some kind of constexpr compatible map? – StarCrunch Aug 31 '20 at 02:33
  • I'm not aware if there is a way to make this in standard c++. I don't think it would be possible since the allocator object in a map handle storage dynamically. However, I've found this C++ header (https://github.com/mapbox/eternal) which implements compile-time maps and hash maps. I haven't tried it myself, though. – emegona Aug 31 '20 at 03:03
  • while the standard library may not support it, at least it is clear how this is possible in principle. Now that I know what to search for there are plenty of examples (until now I thought constexpr was just for compile time expressions, but it makes sense now that it can be leveraged into helping compile time layout as well). Here's one that has constexpr map like objects https://github.com/serge-sans-paille/frozen – StarCrunch Sep 01 '20 at 07:32