很抱歉我使用汇编语言来解释,但我认为这是最好的理解引用的方式。
#include <iostream>
using namespace std;
int main()
{
int i = 10;
int *ptrToI = &i;
int &refToI = i;
cout << "i = " << i << "\n";
cout << "&i = " << &i << "\n";
cout << "ptrToI = " << ptrToI << "\n";
cout << "*ptrToI = " << *ptrToI << "\n";
cout << "&ptrToI = " << &ptrToI << "\n";
cout << "refToI = " << refToI << "\n";
cout << "&refToI = " << &refToI << "\n";
return 0;
}
这段代码的输出如下所示
i = 10
&i = 0xbf9e52f8
ptrToI = 0xbf9e52f8
*ptrToI = 10
&ptrToI = 0xbf9e52f4
refToI = 10
&refToI = 0xbf9e52f8
让我们来看一下反汇编(我使用了GDB。这里的8、9和10是代码行号)
8 int i = 10;
0x08048698 <main()+18>: movl $0xa,-0x10(%ebp)
这里$0xa
是我们要分配给i
的十进制10。而-0x10(%ebp)
表示ebp寄存器
上的内容减去16(十进制)。
-0x10(%ebp)
指向栈上i
的地址。
9 int *ptrToI = &i
0x0804869f <main()+25>: lea -0x10(%ebp),%eax
0x080486a2 <main()+28>: mov %eax,-0x14(%ebp)
将i
的地址分配给ptrToI
。 ptrToI
再次在堆栈上位于地址-0x14(%ebp)
,即ebp
- 20(十进制)。
10 int &refToI = i;
0x080486a5 <main()+31>: lea -0x10(%ebp),%eax
0x080486a8 <main()+34>: mov %eax,-0xc(%ebp)
现在问题来了!比较第9行和第10行的反汇编代码,你会发现-0x14(%ebp)
在第10行被替换为-0xc(%ebp)
。-0xc(%ebp)
是refToI
的地址。它分配在堆栈上。但是你永远无法从你的代码中获取这个地址,因为你不需要知道这个地址。
所以说,引用确实占用内存。在本例中,它是堆栈内存,因为我们将其分配为局部变量。
它占用多少内存?与指针一样多。
现在让我们看看如何访问引用和指针。为简单起见,我只显示了代码片段的一部分。
16 cout << "*ptrToI = " << *ptrToI << "\n";
0x08048746 <main()+192>: mov -0x14(%ebp),%eax
0x08048749 <main()+195>: mov (%eax),%ebx
19 cout << "refToI = " << refToI << "\n";
0x080487b0 <main()+298>: mov -0xc(%ebp),%eax
0x080487b3 <main()+301>: mov (%eax),%ebx
现在比较上述两行代码,你会发现它们非常相似。 -0xc(%ebp)
是refToI
的实际地址,但你永远无法访问它。
简单来说,如果你将引用视为普通指针,那么访问引用就像获取引用指向地址处的值。这意味着以下两行代码将给出相同的结果。
cout << "Value if i = " << *ptrToI << "\n";
cout << "Value if i = " << refToI << "\n";
现在来比较一下:
15 cout << "ptrToI = " << ptrToI << "\n";
0x08048713 <main()+141>: mov -0x14(%ebp),%ebx
21 cout << "&refToI = " << &refToI << "\n";
0x080487fb <main()+373>: mov -0xc(%ebp),%eax
我猜你能看出这里正在发生什么。如果你请求 &refToI
:
- 返回地址为
-0xc(%ebp)
的存储单元中的内容。
-0xc(%ebp)
是 refToI
所在的地址,其内容就是 i
的地址。
最后一件事。为什么这行被注释了?
因为*refToI
是不允许的,会在编译时产生错误。