我试图通过查看编译生成的工件来对使用编译时构造的类进行一些内存使用分析。我用" constexpr"标记了几个类构造函数。并确保他们能够确保编译时的构建。查看映射文件,我可以看到构造函数和析构函数不再包含在.text部分中。
但是,这些课程出现在哪里?我曾假设它们将作为类的静态实例包含在.data部分中,但似乎并非如此。 .text部分已缩小,但所有其他部分似乎都相同。这些数据在哪里?
(我正在使用GCC 5.2.0并创建一个静态链接的ELF。)
编辑:这里有一些示例代码。
#include <stddef.h>
#include <stdint.h>
struct AbstractMemoryAccess
{
virtual uint32_t read() const = 0;
virtual void write(const uint32_t data) const = 0;
};
class ConcerteMemoryAccess : public AbstractMemoryAccess
{
public:
constexpr ConcerteMemoryAccess(const size_t baseAddress)
: _baseAddress(baseAddress)
{
// empty
}
virtual uint32_t read() const
{
return *(volatile uint32_t *)(_baseAddress);
}
virtual void write(const uint32_t data) const
{
*(volatile uint32_t *)(_baseAddress) = data;
}
private:
const size_t _baseAddress;
};
#define ARBITRARY_PERIPHERAL_ADDRESS 0x40001000
int main(void)
{
ConcerteMemoryAccess memoryAccessor(ARBITRARY_PERIPHERAL_ADDRESS);
AbstractMemoryAccess &rAbsMemoryAccessor = memoryAccessor;
while (1)
{
uint32_t readData = rAbsMemoryAccessor.read();
rAbsMemoryAccessor.write(readData);
}
return 0;
}
反编译为:
000006a4 <main>:
6a4: b0004000 imm 16384
6a8: e8601000 lwi r3, r0, 4096
6ac: b0004000 imm 16384
6b0: f8601000 swi r3, r0, 4096
6b4: b800fff0 bri -16 // 6a4 <main>
所以它看起来像内联访问的内联......但对于非平凡的情况是否也是如此?是否对constexpr对象进行内联调用?
答案 0 :(得分:1)
该代码可以去虚拟化,这就是为什么不需要vtable的原因,因此可以将虚拟编译成任何东西。编译器可以看到这两个类,因为它们位于同一个翻译单元中。
使用链接时优化(LTO。)时,还可以在翻译单元之间进行去虚拟化。
所以它看起来像内联访问的内联......但对于非平凡的情况是否也是如此?是否对constexpr对象进行内联调用?
没有。只是在这个例子中,它很容易去除功能的虚拟化。一旦发生这种情况,就不再有virtual
而且没有vtable可以通过,所以非虚拟函数的通常优化就会启动,而且东西可以编译成空气。
答案 1 :(得分:0)
类实例变量不存在于任何地方;它被优化了。
如果它确实存在于任何地方,它将在堆栈上,因为它具有自动存储(main
中的局部变量)。如果构造函数被优化掉但对象仍然必须存在于内存中,那么它可能会被存储到立即数据的堆栈内存中。因此在可执行文件中,它将嵌入到指令流中。
如果对象本身为const
,则gcc更有可能在.rodata
部分中将其优化为静态只读存储。在可执行代码之前或之后,此部分作为可执行文件的text
段的一部分进行链接。 (字符串文字也在.rodata
。)
例如,将指向某个东西的指针传递给外部函数会强制gcc实际将其存储在内存中:
void ext(const char*);
void foo_automatic_nonconst() {
char str [] = "abcdefghijklmnopq";
ext(str);
}
void foo_automatic_const() {
const char str [] = "abcdefghijklmnopq";
ext(str);
}
void foo_static_const() {
static const char str [] = "abcdefghijklmnopq";
ext(str);
}
void foo_static_nonconst() {
static char str [] = "abcdefghijklmnopq";
ext(str);
}
gcc5.2 -O3 for x86-64 on the Godbolt compiler explorer:
foo_automatic_nonconst():
sub rsp, 24
movabs rax, 7523094288207667809
mov QWORD PTR [rsp], rax
mov rdi, rsp
movabs rax, 31365138664352361
mov QWORD PTR [rsp+8], rax
call ext(char const*)
add rsp, 24
ret
foo_automatic_const():
... same asm as automatic_nonconst, unfortunately.
# I think it could have used a constant like static_const
foo_static_const():
mov edi, OFFSET FLAT:foo_static_const()::str
jmp ext(char const*)
foo_static_nonconst():
mov edi, OFFSET FLAT:foo_static_nonconst()::str
jmp ext(char const*)
.section .data
foo_static_nonconst()::str:
.string "abcdefghijklmno"
.section .rodata
foo_static_const()::str:
.string "abcdefghijklmno"
gcc将优化浮点常量到静态存储中:
float times3(float f) { return f * 3.0; }
mulss xmm0, DWORD PTR .LC4[rip]
ret
.section .rodata
.LC4:
.long 1077936128
这同样适用于向量常量(例如_mm_set_epi32(1.5, 3.0, 4.5, 6.0);
与英特尔的SSE内在函数。)
这并非直接回答您的问题,但也许它会让您了解其他事情。类和其他类型的对象之间不应该有太大区别,至少在gcc可以去虚拟化的情况下。