在他的书Optimizing Software in C++中,Agner Fog给出了以下例子:
字符串常量和浮点常量存储在static中 记忆。例如:
// Example 7.2 a = b * 3.5; c = d + 3.5;
这里,常量3.5将存储在静态存储器中。大多数编译器 会认识到这两个常数是相同的,所以只有一个 不断需要存储。
所有浮点常量始终是否都存储在静态内存中?
为什么它们不能存储在堆栈上?在他给出的例子中说。
答案 0 :(得分:1)
我基于你的伪代码创建了一个简单的工作示例:
int main()
{
double b = 3.0;
double d = 3.14;
double a = b * 3.5;
double c = d + 3.5;
return 0;
}
然后编译它:
g++ -O0 -o const const.cc
然后在调试器中使用它:
gdb const
...
(gdb) disass main
Dump of assembler code for function main:
0x00000000004004ed <+0>: push %rbp
0x00000000004004ee <+1>: mov %rsp,%rbp
0x00000000004004f1 <+4>: movabs $0x4008000000000000,%rax
0x00000000004004fb <+14>: mov %rax,-0x20(%rbp)
0x00000000004004ff <+18>: movabs $0x40091eb851eb851f,%rax
0x0000000000400509 <+28>: mov %rax,-0x18(%rbp)
0x000000000040050d <+32>: movsd -0x20(%rbp),%xmm1
0x0000000000400512 <+37>: movsd 0xae(%rip),%xmm0 # 0x4005c8
0x000000000040051a <+45>: mulsd %xmm1,%xmm0
0x000000000040051e <+49>: movsd %xmm0,-0x10(%rbp)
0x0000000000400523 <+54>: movsd -0x18(%rbp),%xmm1
0x0000000000400528 <+59>: movsd 0x98(%rip),%xmm0 # 0x4005c8
0x0000000000400530 <+67>: addsd %xmm1,%xmm0
0x0000000000400534 <+71>: movsd %xmm0,-0x8(%rbp)
0x0000000000400539 <+76>: mov $0x0,%eax
0x000000000040053e <+81>: pop %rbp
0x000000000040053f <+82>: retq
End of assembler dump.
(gdb) b main
Breakpoint 1 at 0x4004f1
(gdb) r
Starting program: /home/sasha/stackoverflow/const
Breakpoint 1, 0x00000000004004f1 in main ()
(gdb) p *(double*)0x4005c8
$2 = 3.5
所以你可以从所有这些中看到,常量3.5存储在地址0x4005c8
中,在main
结束后只有136个字节。两次使用相同的地址来引用它,尽管它在反汇编中显示不同 - 第一次为0xae(%rip)
,第二次为movsd 0x98(%rip)
。这是因为rip
的值随着执行的进行而不断变化 - 它是指令指针。
在objdump -t
的帮助下,您可以看到上述地址属于.rodata
部分。
请注意,3.0和3.14常量是在没有内存引用的情况下显式编码的,每个:
movabs $0x4008000000000000,%rax
和
movabs $0x40091eb851eb851f,%rax
显然,因为它们没有被重复gcc
所以决定将它们存储在内存中是不值得的。
更新:正如JSF所指出的那样,不使用存储而非直接常量的决定确实取决于使用情况。我确认如果我将double d = 3.14
更改为double d = b + 3.14
,则3.14将成为内存引用。
我使用-O0
来保持简单。通过这样一个简单示例中的优化,程序集被优化为可以获得相同结果的东西,但其方式与有用的东西相距甚远,无法说明这一点。
答案,至少在gcc
的情况下,似乎确实是&#34;它取决于&#34;。但是,如果你很好奇,不仅仅是信任一本书或一篇博文,最好是做一个简单的例子并把它分开 - 你从马的口中直接得到它,你学到了很多东西更多。
答案 1 :(得分:1)
谢谢你的例子。你是正确的,当使用gcc编译64位模式且优化关闭(-O0)时,它会将常量存储在代码中。如果编译为32位模式,它会将常量存储在内存中。
如果启用优化(-O3),那么计算将被简化优化,因为它们未被使用,而main只返回0.
如果你创建一个执行某些浮点计算并返回float或double的函数,并在优化时编译,那么你会看到浮点常量存储在静态内存中。
我试过了:
float test(float x) {
return x*0.9f + 3.1f;
}
g ++ -c -S -O3 test.cpp
cat test.s
得到了:
mulss .LC0(%rip), %xmm0
addss .LC1(%rip), %xmm0
ret
其中.LC0和.LC1是只读数据段中的常量。不幸的是,x86和x86-64指令集没有在浮点寄存器或向量寄存器中存储立即数的指令。它必须首先将常量存储在通用寄存器中,然后将其传输到xmm寄存器,这不是最佳的(除了数据高速缓存负载较重但代码高速缓存不存在的情况)。