今天没事回顾了一下以前写的东西,发现这一篇写的模棱两可
https://blog.youkuaiyun.com/niino/article/details/6286558
时隔多年,再来回来看这篇文章只能说还是太年轻,看待问题太片面了
问题确实是出在强转上,而且也确实会产生一个临时变量。
如何证明产生了临时变量?只需要将 const int &tt 的 const 修饰符去掉,就编译不过了,这时候会报错
Non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int'
非const修饰的左值int类型引用不能绑定到一个int类型的临时变量
而且通过汇编代码,也可以更明显的看出来.先补一下汇编的基础知识
rbp 堆栈基指针
eax 32位累加寄存器
rax 64位累加寄存器
movl src dst 32位移动
movq src dst 64位移动
leaq src dst 取src的地址放入dst
我们可以先简化一下源代码,将强转直接放到 main 函数里,再来看汇编
#include <stdio.h>
#include <stdlib.h>
int main()
{
int d = 20;
const int& p = (int)d;
printf("4. d = %d, d:%p\n", d, &d);
printf("4. p = %d, p:%p\n", p, &p);
return 0;
}
输出:
4. d = 20, d:0x7fffc1011e88
4. p = 20, p:0x7fffc1011e8c
可以看到地址不同,我们再生成汇编代码
g++ -S -fverbose-asm -O0 test.cpp -o test.s
test.s 内容如下:
...
# test.cpp:5: {
movq %fs:40, %rax #, tmp97
movq %rax, -8(%rbp) # tmp97, D.3696
xorl %eax, %eax # tmp97
# test.cpp:6: int d = 20;
movl $20, -24(%rbp) #, d
# test.cpp:7: const int& p = (int)d;
movl -24(%rbp), %eax # d, _6
movl %eax, -20(%rbp) # _6, D.3689
leaq -20(%rbp), %rax #, tmp92
movq %rax, -16(%rbp) # tmp92, p
# test.cpp:8: printf("4. d = %d, d:%p\n", d, &d);
movl -24(%rbp), %eax # d, d.0_1
leaq -24(%rbp), %rdx #, tmp93
movl %eax, %esi # d.0_1,
leaq .LC0(%rip), %rdi #,
movl $0, %eax #,
call printf@PLT #
# test.cpp:9: printf("4. p = %d, p:%p\n", p, &p);
movq -16(%rbp), %rax # p, tmp94
movl (%rax), %eax # *p_8, _2
movq -16(%rbp), %rdx # p, tmp95
movl %eax, %esi # _2,
leaq .LC1(%rip), %rdi #,
movl $0, %eax #,
call printf@PLT #
# test.cpp:10: return 0;
movl $0, %eax #, _11
# test.cpp:11: }
...
我们去掉强转部分的代码:
...
int main()
{
int d = 20;
const int &p = d;
...
return 0;
}
再生成汇编
去掉强转后的汇编
# test.cpp:6: int d = 20;
movl $20, -20(%rbp) #, d
# test.cpp:7: const int& p = d;
leaq -20(%rbp), %rax #, tmp91
movq %rax, -16(%rbp) # tmp91, p
加上强转后的汇编
# test.cpp:6: int d = 20;
movl $20, -24(%rbp) #, d
# test.cpp:7: const int& p = (int)d;
movl -24(%rbp), %eax # d, _6
movl %eax, -20(%rbp) # _6, D.3689
leaq -20(%rbp), %rax #, tmp92
movq %rax, -16(%rbp) # tmp92, p
可以发现,变量 d 的地址偏移在两个版本上是不同的,一个是在rbp地址上偏移20,一个是偏移24,正好多出4个字节(一个int临时变量).
没有强转的代码分析:
根据上下文, -20(%rbp) 表示变量 d 的地址, -16(%rbp) 表示变量 p 的地址
leaq -20(%rbp), %rax 计算 d 的地址,放到 rax 寄存器
movq %rax, -16(%rbp) 把 rax 中的内容, 放到 -16(%rbp) 中
所以 p 保存的是 d 的地址
强转后的代码分析:
根据上下文 -24(%rbp) 表示 d 的地址, -16(%rbp) 表示 p 的地址
movl -24(%rbp), %eax 将 d 的地址,放到 eax 寄存器中
movl %eax, -20(%rbp) 将 eax 的内容放到 -20(%rbp) 位置上,这里产生了临时变量 -20(%rbp)
leaq -20(%rbp), %rax 计算 -20(%rbp)的地址,放入 rax
movq %rax, -16(%rbp) 将 rax 的内容放到 p 的寄存器中,此时 p 指向临时变量的地址
上面是基础类型 int 的分析,我们可以再来看看自建类型
#include <stdio.h>
#include <stdlib.h>
class MyClass
{
public:
MyClass() {
printf("Constructor...\n");
this->val = 0;
}
MyClass(const MyClass &c) {
printf("Copy Constructor...\n");
this->val = c.val;
}
~MyClass() {
printf("Destructor...\n");
}
private:
int val;
};
int main()
{
MyClass d;
const MyClass &p = (MyClass)d;
printf("d:%p\n", &d);
printf("p:%p\n", &p);
return 0;
}
输出:
Constructor...
Copy Constructor...
d:0x7fffd98fa108
p:0x7fffd98fa10c
Destructor...
Destructor...
可以看到地址是不同的,由于自建类型的汇编代码太多,偷个懒.首先我们猜测在强转中产生了临时变量,那么根据C++的规则,那么必然会调用拷贝构造函数,所以我们定义一个拷贝构造函数,然后加上输出(如上图),可以看到在中间确实调用了拷贝构造,自然坐实了强转会产生临时变量的说法.这也是我们前面提到,不加 const 编译报错的由来. 只有使用 const,才能持有一个临时变量的地址.
默认的引用赋值语句,如
int d = 20;
int &p = d;
MyClass a;
MyClass &p = a;
会直接使用右值的地址为左值引用赋值, 但是如果加上类型强转,则会破坏这种语义的解析过程,即使是同类型的强转,在代码编写来看,是一种冗余的保护,但是编译器会一视同仁.所以在进行类型强转时,务必注意这一点。