请考虑以下代码:
使用带有-O3的g ++ 4.6编译,我可以看到编译器在函数调用之间重新读取“a”的值。将a的定义更改为“const int”,编译器不会这样做,而是直接加载立即值“12”到edi中。如果a不是常量,但我更改func1的签名为按值接受,则情况也是如此。
虽然这不是代码生成中的错误,但仍然是奇怪的行为。由于func1承诺不更改a,为什么编译器的代码会根据a是否为const而改变呢?
编辑:一些怀疑者声称我可能读错了代码。以上代码使用-S编译产生以下结果:
void func1(const int &i);
void func2(int i);
void f()
{
int a=12;
func1(a);
func2(a);
}
使用带有-O3的g ++ 4.6编译,我可以看到编译器在函数调用之间重新读取“a”的值。将a的定义更改为“const int”,编译器不会这样做,而是直接加载立即值“12”到edi中。如果a不是常量,但我更改func1的签名为按值接受,则情况也是如此。
虽然这不是代码生成中的错误,但仍然是奇怪的行为。由于func1承诺不更改a,为什么编译器的代码会根据a是否为const而改变呢?
编辑:一些怀疑者声称我可能读错了代码。以上代码使用-S编译产生以下结果:
_Z1fv:
.LFB0:
.cfi_startproc
subq $24, %rsp
.cfi_def_cfa_offset 32
leaq 12(%rsp), %rdi
movl $12, 12(%rsp)
call _Z5func1RKi
movl 12(%rsp), %edi <-- Rereading a
call _Z5func2i
addq $24, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
将a改成const,会产生以下结果:
_Z1fv:
.LFB0:
.cfi_startproc
subq $24, %rsp
.cfi_def_cfa_offset 32
leaq 12(%rsp), %rdi
movl $12, 12(%rsp)
call _Z5func1RKi
movl $12, %edi <-- Use immediate value
call _Z5func2i
addq $24, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
func1
可以使用const_cast
去除常量性并写入i
,而且这实际上是合法的(即不是未定义行为),因为底层对象a
没有被声明为const
。 - T.C.func1
本身并不知道任何事情。它只是一个文本文件中的一堆字符。当调用函数时,编译器知道a
是什么。 - Arne Mertz-S
编译并发布汇编代码,而不是告诉我们你对代码的解释,让我们在黑暗中摸索。这尤其重要,因为你的描述令人怀疑...调用func1(const int&)
意味着需要将2压入堆栈并传递地址,而func2(a)
则意味着需要实际“读取”2并将其作为参数传递,所以当你说“重新阅读...之间”的时候,也许它的意义是第一次读取。 - Tony Delroymovl 12(%rsp), %edi
并不是“重新读取”该值...这是客户端代码中的第一次且唯一一次读取,在计算堆栈地址(leaq
)和写入(movl $12,...
)之后。微不足道的区别,但我想确保您没有看到两个读取,那将会很奇怪。尽管如此,为什么只有第二个movl $12, %edi
是一个好问题...(并已得到很好的回答)+1。干杯。 - Tony Delroy