没有运行时初始化的 C++ 中的全局 const 对象
global const objects in cpp with no runtime initialization
如何在 cpp 中声明一个全局 const 对象,以便其关联数据完全存储在 .rodata 中,而不是在 运行 初始化期间创建并不必要地复制的对象?
例如,如果我创建
类型的全局变量
const std::string
const std::array<const std::string, 4>
const std::map<std::string, const std::string>
测试显示这些将编译成 .bss,因此需要 运行time 初始化,尽管它是编译时已知的常量数据......并且它需要知道将它们初始化为什么,所以它是还不必要地复制数据,使用额外的内存。
如何在没有任何 运行时间初始化的情况下获取驻留在 .rodata 中的实际 const 对象?
由于 C++ 标准可能对此不够具体,如果您需要一些特定于编译器的功能,g++ and/or clang++ 支持的功能将不胜感激。
注意:请,如果您的答案是某种提升或某种特定库的东西,请解释该库是如何实现的。我想了解这是如何完成的。
以下注释可以忽略,但包括在内,因为我不断从人们那里得到的初始反应是“不可能,const 字符串或 const 数组不需要 运行 时间初始化或重复数据”。
下面是一个示例和一些测试:
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;
}
编译并检查对象的放置位置
$ 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
您也可以直接 运行 程序并查看输出。
或者看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
您可以通过定义支持它的构造函数来定义 compile-time 处的对象。注意对象的成员也应该是const
.
在下面的例子中,我们在编译时创建了一个对象foo
。我们可以使用 static_assert
函数来验证这一点。另一方面,foo2 是在运行时创建的。不幸的是,您的问题没有灵丹妙药。使用 constexpr
不能保证你总是以 compile-time 初始化结束,因为这只会在可能的情况下发生,如果没有发生,编译器不会抱怨。
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');
}
如何在 cpp 中声明一个全局 const 对象,以便其关联数据完全存储在 .rodata 中,而不是在 运行 初始化期间创建并不必要地复制的对象?
例如,如果我创建
类型的全局变量const std::string
const std::array<const std::string, 4>
const std::map<std::string, const std::string>
测试显示这些将编译成 .bss,因此需要 运行time 初始化,尽管它是编译时已知的常量数据......并且它需要知道将它们初始化为什么,所以它是还不必要地复制数据,使用额外的内存。
如何在没有任何 运行时间初始化的情况下获取驻留在 .rodata 中的实际 const 对象?
由于 C++ 标准可能对此不够具体,如果您需要一些特定于编译器的功能,g++ and/or clang++ 支持的功能将不胜感激。
注意:请,如果您的答案是某种提升或某种特定库的东西,请解释该库是如何实现的。我想了解这是如何完成的。
以下注释可以忽略,但包括在内,因为我不断从人们那里得到的初始反应是“不可能,const 字符串或 const 数组不需要 运行 时间初始化或重复数据”。
下面是一个示例和一些测试:
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;
}
编译并检查对象的放置位置
$ 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
您也可以直接 运行 程序并查看输出。
或者看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
您可以通过定义支持它的构造函数来定义 compile-time 处的对象。注意对象的成员也应该是const
.
在下面的例子中,我们在编译时创建了一个对象foo
。我们可以使用 static_assert
函数来验证这一点。另一方面,foo2 是在运行时创建的。不幸的是,您的问题没有灵丹妙药。使用 constexpr
不能保证你总是以 compile-time 初始化结束,因为这只会在可能的情况下发生,如果没有发生,编译器不会抱怨。
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');
}