参考如何在内部实现?

只是想知道在不同的编译器和debugging/发布configuration中它是如何实现的。 标准是否提供了有关其实施的build议? 它有什么不同吗?

我试图运行一个简单的程序,我已经从函数返回非const引用和指向局部variables的指针,但是它的工作方式相同。 那么,内部引用只是一个指针吗?

一个引用的自然实现确实是一个指针。 但是,不要依赖于你的代码。

为了重复大家所说的一些东西,让我们来看看一些编译器输出:

 #include <stdio.h> #include <stdlib.h> int byref(int & foo) { printf("%d\n", foo); } int byptr(int * foo) { printf("%d\n", *foo); } int main(int argc, char **argv) { int aFoo = 5; byref(aFoo); byptr(&aFoo); } 

我们可以用LLVM编译这个(优化closures),我们得到以下结果:

 define i32 @_Z5byrefRi(i32* %foo) { entry: %foo_addr = alloca i32* ; <i32**> [#uses=2] %retval = alloca i32 ; <i32*> [#uses=1] %"alloca point" = bitcast i32 0 to i32 ; <i32> [#uses=0] store i32* %foo, i32** %foo_addr %0 = load i32** %foo_addr, align 8 ; <i32*> [#uses=1] %1 = load i32* %0, align 4 ; <i32> [#uses=1] %2 = call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 %1) ; <i32> [#uses=0] br label %return return: ; preds = %entry %retval1 = load i32* %retval ; <i32> [#uses=1] ret i32 %retval1 } define i32 @_Z5byptrPi(i32* %foo) { entry: %foo_addr = alloca i32* ; <i32**> [#uses=2] %retval = alloca i32 ; <i32*> [#uses=1] %"alloca point" = bitcast i32 0 to i32 ; <i32> [#uses=0] store i32* %foo, i32** %foo_addr %0 = load i32** %foo_addr, align 8 ; <i32*> [#uses=1] %1 = load i32* %0, align 4 ; <i32> [#uses=1] %2 = call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 %1) ; <i32> [#uses=0] br label %return return: ; preds = %entry %retval1 = load i32* %retval ; <i32> [#uses=1] ret i32 %retval1 } 

两个function的主体是相同的

对不起,使用程序集来解释这一点,但我认为这是理解编译器如何实现引用的最好方法。

  #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 register -16(十进制)的内容。 -0x10(%ebp)指向i在栈上的地址。

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

将地址分配给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中-0xc(%ebp)-0x14(%ebp)替代。- -0xc(%ebp)refToNum的地址。 它被分配在堆栈上。 但是你永远无法从你的代码中得到这个地址,因为你不需要知道地址。

所以; 一个参考确实占据着记忆。 在这种情况下,它是堆栈内存,因为我们已经将它分配为局部variables。 它占用了多less内存? 一个指针占据多less。

现在让我们看看我们如何访问引用和指针。 为了简单,我只显示了部分的程序集片段

 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所在的位置,其内容refToi i地址。

最后一件事,为什么这条线被评论?

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

因为*refToI是不允许的,它会给你一个编译时错误。

我不能说这是正确的,但我做了一些谷歌search,并发现这样的说法:

语言标准不需要任何特定的机制。 只要行为符合规定,每个实现都可以以任何方式自由执行。

来源: Bytes.com

没有必要引用一个指针。 在许多情况下,它是,但在其他情况下,它只是一个别名,不需要为指针分配单独的内存。 程序集样本并不总是正确的,因为它们在很大程度上取决于优化以及编译器的“智能”程度。

例如:int i; int&j = i;

不需要生成任何额外的代码或分配任何额外的内存。

用Bjarne的话来说:

就像一个指针一样, 引用是一个对象的别名,通常被实现来保存一个对象的机器地址 ,并且不会像指针那样强加性能开销,但是它不同于指针:

•使用与对象名称完全相同的语法访问引用。

引用总是指它被初始化的对象。

•没有“空引用”,我们可能会认为引用是指对象


虽然引用实际上是一个指针 ,但不应该像指针一样使用,而应该用作别名

引用不是指针。 这是事实。 指针可以绑定到另一个对象,有自己的操作像解引用和递增/递减。

尽pipe在内部,引用可以作为指针来实现。 但是这是一个实现细节,不会改变引用不能与指针交换的事实。 而且假设引用被实现为指针,就不能编写代码。