当我学习C++引用时,听到的第一句话是“引用是变量的别名,不像指针一样需要占用内存空间”。然而学到深处,发现此话并不完全正确。
本文主要介绍我如何通过实验来了解到C++引用的实现,其实引用的内部就是指针。当然这也于编译器有关,所以这里需要提及一下测试所用的编译器及环境。
测试环境是MinGW的g++ 8.1.0,64位编译器,64位的机子。所以指针的大小是8个字节,即64个bit。(注:因为目的是测试,所以测试时并没有处理对new操作符所产生对象的回收)
首先我写出了如下代码,试图通过指针偏移来获取有关引用的信息:
#include <iostream>
#include <string>
using namespace std;
int main() {
int64_t x;
string& str = *new string();
int64_t y;
cin >> str; // 对引用做一次操作,避免编译器把变量优化掉
cout << &x << endl;
cout << &y << endl;
cout << str << endl;
return 0;
}
然而,这个程序的输出如下(str的输出忽略):
0x61fe00
0x61fdf8
难道引用真的不占内存?编译器真的很聪明,可能优化掉了吧;经过一系列尝试,我写出了另外一段代码:
#include <iostream>
#include <string>
using namespace std;
void foo(int64_t q, string& s, int64_t r) {
cout << "&q: " << &q << endl;
cout << "&r: " << &r << endl;
cout << "*(string**)(&q + 1): " << *(string**)(&q + 1) << endl;
}
int main()
{
string& str = *new string();
cout << "main(): " << &str << endl;
foo(0, str, 0);
return 0;
}
这段代码的输出是:
main(): 0x1e1bd0
&q: 0x61fde0
&r: 0x61fdf0
*(string**)(&q + 1): 0x1e1bd0
可见,q的地址是0x61fde0
,r的地址是0x61fdf0
。两个地址间相差16个字节!这里引用占用的内存出来了。显然引用对应的指针存储在q的8个字节之后。我们可以将q的地址加1,也就是加上8个字节,这里存储的就是引用的信息。假设它就是指针,那么考虑:(&q + 1)
本身是一个指向string*
的指针,也就是string**
。所以若要获取指针的值,需要对这个值解一次引用,输出出来。(当然如果你想简单一点,可以直接把它转成int64_t
然后用16进制输出亦可)
至此真相大白,程序输出的最后一行0x1e1bd0
与主函数中new出来的对象的地址(见输出第一行)一致。所以得出结论:引用是用指针实现的。用户对引用的访问操作都内含一次解引用,而这对用户来说是透明的。
不过需要提及的是,回想本文的第一个测试,发现引用的指针空间被优化掉了。所以引用有时也不一定会在栈上真正以指针体现出来。