C++中引用是如何内部存储的?

14

我想知道引用是如何在内部存储的?我觉得对这个层次有深入的理解,将使我更好地理解指针与引用的概念,并做出正确的决策选择。

我怀疑它的工作原理与指针基本相同,但编译器负责处理指针。请给予建议。


6
“我感觉只有深入理解指针和引用的概念,才能更好地理解它们之间的区别。”“我觉得那并没有什么帮助。” - Mr.Anubis
1
你可以尝试这个链接:http://eetimes.com/discussion/programming-pointers/4023307/References-vs-Pointers - Vlad
1
指针和引用是访问远程/不同内存位置的一种方式。使用指针时,这是显式的。您知道您正在指向某些东西,因此必须小心。使用引用时,编译器会为您处理内部操作。因此,指针-> 显式,引用-> 隐式。它们之间还有更多的区别,但这是基本区别。 - prathmesh.kallurkar
1
可能是如何使用C++引用引用是如何在内部实现的?的重复问题。 - jww
@tripleee - 你能否帮忙审核一下这个关闭的问题吗? - jww
显示剩余3条评论
3个回答

16
没有要求引用必须以任何方式“存储”。就语言而言,引用只是某个现有对象的别名,这是任何编译器必须提供的全部内容。
如果引用只是某个已经在作用域内的其他对象的简写,或者带有引用参数的函数被内联,那么完全可以不需要存储任何东西。
在需要显式表示引用的情况下(例如在调用不同翻译单元中的函数时),您可以将 T & x 实际实现为 T * const,并将每个出现的 x 视为隐式解引用该指针。即使在更高级别上,您也可以认为 T & x = y; 和 T * const p = &y;(相应地是 x 和 *p)本质上是等效的,因此这将是实现引用的一种明显方式。
但是当然没有要求,任何实现都可以自由地做任何它想做的事情。

我明白了。从你的帖子中,我认为你是在说,使用引用时,不需要分配任何额外的变量,只需将名称添加到您正在维护的变量表中(尽管这是我的假设)。我不知道C ++现在如何工作,但如果我实现一个新的面向对象编程语言,我仍然可以确保在内存中不分配任何额外的变量,只需记住变量'a'有另一个名字叫“a_ref”????我理解得对吗? - howtechstuffworks
1
只要引用仅涉及本地或成员范围变量,那么是的。 - Puppy

13

引用和指针在编译器内部都是别名,对待方式相同。

但是从用户的角度来看,两者之间存在几个微妙的差异。

其中一些主要的区别包括:

  • 指针可以是NULL,而引用不能。不存在所谓的NULL引用。
  • const引用可以延长与其绑定的临时对象的生命周期,这在指针中没有类似物。

此外,引用与const指针(而不是指向常量的指针)有一些共同点:

  • 必须在创建时初始化引用。
  • 引用永久绑定到单个存储位置,不能在以后重新绑定。

如果你知道你有一个对象需要引用,并且永远不想引用其他任何东西,请使用引用,否则请使用指针。


5
如果你瞄准得当,你会自己打到脚上。 - Captain Giraffe
2
@SigTerm 这本来就是未定义行为。 - Etienne de Martel
4
@SigTerm 我没有什么可证明的,只需要去读一下标准。问题出在 *((int*)0) 上:你正在解引用一个空指针。你很幸运它没有立即崩溃。如果它没有崩溃,那么是的,你总是可以将其绑定到引用,但这并不能改变一开始解引用指针就是未定义行为这个事实。 - Etienne de Martel
2
@SigTerm,我没想到我居然要为这么显而易见的事情引用标准。 - Etienne de Martel
2
@SigTerm:请停止争论不存在的愚蠢问题。你添加了一个根本不正确的参数,许多用户告诉你为什么它是错误的,如果你仍然想争辩它的有效性,你应该提出一份标准的反驳证明你的观点,否则你应该接受错误并从中学习,继续前进。在我看来,这应该是在这里的整个目的,学习和改善自己,同时试图帮助他人。为了争论而争论是毫无意义的,请成熟或者如果你已经成熟,请改变态度。 - Alok Save
显示剩余8条评论

2

很抱歉要用汇编来解释这个问题,但我认为这是理解编译器如何实现引用的最佳方式。

#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 << "refToNum = " << refToI << "\n";
    //cout << "*refToNum = " << *refToI << "\n";
    cout << "&refToNum = " << &refToI << "\n";

    return 0;
}

这段代码的输出结果如下:
i = 10
&i = 0xbf9e52f8
ptrToI = 0xbf9e52f8
*ptrToI = 10
&ptrToI = 0xbf9e52f4
refToNum = 10
&refToNum = 0xbf9e52f8

让我们来看一下反汇编(我使用的是GDB。这里的8、9和10是代码的行号)

8           int i = 10;
0x08048698 <main()+18>: movl   $0xa,-0x10(%ebp)

这里$0xa是我们要分配给i的十进制数10。 -0x10(%ebp)表示ebp寄存器的内容减去16(decimal)。 -0x10(%ebp)指向堆栈上i的地址。

9           int *ptrToI = &i;
0x0804869f <main()+25>: lea    -0x10(%ebp),%eax
0x080486a2 <main()+28>: mov    %eax,-0x14(%ebp)

i的地址分配给ptrToIptrToI再次位于堆栈上,位于地址-0x14(%ebp),即ebp - 20(十进制)。

10          int &refToI = i;
0x080486a5 <main()+31>: lea    -0x10(%ebp),%eax
0x080486a8 <main()+34>: mov    %eax,-0xc(%ebp)

现在注意!比较第9行和第10行的反汇编代码,您会发现第10行中的-0x14(%ebp)-0xc(%ebp)所替换。 -0xc(%ebp)是refToNum的地址。 它在堆栈上分配。 但您永远无法从您的代码中获取此地址,因为无需知道该地址。
所以,引用确实占用内存。 在这种情况下,它是堆栈内存,因为我们将其分配为局部变量。 它占用多少内存?与指针占用的一样多。
现在让我们看看如何访问引用和指针。 为简单起见,我只显示了部分汇编片段。
16          cout << "*ptrToI = " << *ptrToI << "\n";
0x08048746 <main()+192>:        mov    -0x14(%ebp),%eax
0x08048749 <main()+195>:        mov    (%eax),%ebx
19          cout << "refToNum = " << 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 << "&refToNum = " << &refToI << "\n";
0x080487fb <main()+373>:        mov    -0xc(%ebp),%eax

我想你能够看出这里发生了什么。如果你请求&refToI,那么将返回-0xc(%ebp)地址位置的内容,-0xc(%ebp)refToi所在的位置,它的内容就是i的地址。

最后一件事,为什么这行代码被注释掉了?

//cout << "*refToNum = " << *refToI << "\n";

因为*refToI是不允许的,这会导致编译时出错。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接