C++变量的引用是否占用内存空间?

1
2
int a = 10;
int &b = a;

根据引用的定义,b作为a的别名,不单独占用内存空间。如果取b和a的地址,会发现它们是相同的。

但是程序是怎么知道b是指向a的呢?如果b是个指针,程序将开辟一块内存空间,存储“b指向a”这个信息。如果没有一块内存空间来存储“b指向a”这一事实,程序读到b时应该不知所措,怎么能顺利知道b时a的别名呢?

实际上,b在内存上是占用了一块内存空间的。不过编译器对它进行了一些处理,使得程序认为它不单独占用内存空间,且取其地址时直接取到所指向的地址。实际在内存空间上,引用本身也占用一块内存,里面存储着所引用的变量的地址,大小与指针相同,字面上也表现为unsigned long int类型。只是经过编译器处理后,访问这块内存时将直接转而访问其指向的内存。因此在程序中无法读取这块内存本身。

这可以理解为“编译器的把戏”或“程序的谎言”,但这一机制不是为了捉弄程序员,而是为了真正实现别名的效果。

综上:引用的实现实际上是占用内存空间的,但程序把它按照不占用内存空间来处理。

在不同编译器中,引用的实现方式可能有不同。C++语言本身对此实现机制并无说明。因此上述实现机制可能仅适用于部分编译器。

补充:引用的“不占用内存”和宏定义的“不占用内存”不是一回事。二者都可以理解为“别名”,但引用是在程序运行过程中声明的,属于程序运行的层面;而宏定义是代码编译层面的,类似于对代码文本进行“全文替换”,不涉及程序运行,是真正的不占用内存。

引用作为成员变量

引用作为成员变量,正常情况下取获得它的地址,得到的都是其指向对象的地址。但是通过某些骚操作(万能的指针),还是可以获取引用的真实地址,还可以改变引用的指向。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

#include <iostream>

using namespace std;

class Object {
public:
int &reference;
int *pointer;

Object(int &r, int *p) : reference(r), pointer(p) {}
};

int main() {
int num = 12;
int tem = 199;
Object o(num, &num);

cout << "num的地址: " << &num << endl;
cout << "正常情况下获取引用类型的地址: " << &o.reference << " " << &o.pointer << endl;

long long *p = (long long *) (&o.pointer - 1);
cout << "\n通过指针获取引用的地址: " << p << endl;
cout << "解引用获得num的地址: " << (long long *) (*p) << endl;
cout << "原来的o.reference: " << o.reference << endl;

long long temAddress = (long long) &tem;
*p = temAddress;
cout << "现在的o.reference: " << o.reference << endl;
}

num的地址: 0x16b4dee8c
正常情况下获取引用类型的地址: 0x16b4dee8c 0x16b4dee80

通过指针获取引用的地址: 0x16b4dee78
解引用获得num的地址: 0x16b4dee8c
原来的o.reference: 12
现在的o.reference: 199

从上述输出可以知道,正常去获取一个引用成员变量的地址,得到的一定是它指向对象的地址。虽然我们不能得到引用成员变量的地址,但我们可以获取一个指针成员变量的地址,通过上述代码,我们知道引用变量占用一个指针大小的内存空间,所以我们可以对某些已知成员变量的地址进行一定的偏移便可以得到真实的引用类型地址。