我根据您的伪代码创建了一个简单的工作示例:
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 的情况下,答案似乎确实是“视情况而定”。但是,如果你好奇,与其相信一本书或一篇博文,不如做一个简单的例子并把它拆开——你直接从马嘴里得到它,你会学到更多。