一个C ++引用如何看,记忆明智?

鉴于:

int i = 42; int j = 43; int k = 44; 

通过查看variables地址,我们知道每个variables占用4个字节(在大多数平台上)。

但是,考虑到:

 int i = 42; int& j = i; int k = 44; 

我们将看到variablesi确实需要4个字节,但是j取不到任何值,并且k在栈上取4个字节。

这里发生了什么? 看起来j在运行时是不存在的。 那么作为一个函数参数我收到的引用呢? 这必须在堆栈上占用一些空间

虽然我们在这 – 为什么我不能定义一个数组或引用?

 int&[] arr = new int&[SIZE]; // compiler error! array of references is illegal 

在遇到引用j的地方,它被replace为i的地址 。 所以基本上参考内容的地址在编译时就被parsing了,而且在运行的时候不需要像指针那样对其进行解引用。

只是为了澄清我的意思是我的地址

 void function(int& x) { x = 10; } int main() { int i = 5; int& j = i; function(j); } 

在上面的代码中, j不应该在主堆栈上占用空间,但函数的引用x将在堆栈上占据一席之地。 这意味着当以j作为参数调用函数时,将会将函数堆栈中的i的地址 。 编译器可以而且不应该为主堆栈预留空间。

对于数组部分的标准说::

C ++标准8.3.2 / 4:

应该没有对引用的引用,没有引用数组,也没有指向引用的指针。

为什么引用数组是非法的?

一个C ++引用如何看,记忆明智?

它不。 C ++标准只说明它应该如何performance,而不是应该如何实现。

在一般情况下,编译器通常将引用实现为指针。 但是他们通常会有更多关于参考资料可能指向的信息,并将其用于优化。

请记住,引用的唯一要求是它的行为就像引用对象的别名。 所以如果编译器遇到这样的代码:

 int i = 42; int& j = i; int k = 44; 

它所看到的不是“创build一个指向variablesi的指针”(尽pipe编译器可能会select在某些情况下实现它),而是“在符号表中记下j现在是i的别名。 “

编译器不必为j创build一个新的variables,它只需要记住,从现在开始j引用,它应该真的交换出来,而不是使用i

至于创build一个引用数组,你不能这样做,因为它是没用的和毫无意义的。

当你创build一个数组时,所有的元素都是默认构造的。 这是什么意思默认构build一个参考? 它指向什么? 引用中的全部内容是,它们被重新初始化为引用另一个对象,之后它们不能被重新设置。

所以如果可以做的话,你最终会得到一个没有任何引用的数组。 而且你不能改变它们来引用一些东西,因为它们已经被初始化了。

实际上,引用相当于一个指针,除了允许使用引用的额外约束可以允许编译器在更多情况下“优化它”(取决于编译器的智能程度,优化设置,等等当然)。

你不能定义一个引用数组,因为没有语法来初始化它们。 C ++不允许未初始化的引用。 至于你的第一个问题,编译器没有义务为不必要的variables分配空间。 没有办法让j指向另一个variables,所以它实际上只是我在函数作用域中的一个别名,这就是编译器对待它的方式。

只有在其他地方才提到的东西 – 如何让编译器将一些存储空间用于引用:

 class HasRef { int &r; public: HasRef(int &n) : r(n) { } }; 

这就使编译器不能将其视为编译时别名(同一存储的另一个名称)。

对不起,使用程序集来解释这个,但我认为这是理解引用的最好方法。

  #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是不允许的,它会给你一个编译时错误。

直到他们需要物理performance(即作为一个聚合的成员)之前,参考实际上并不存在。

有一系列的参考是非法的,可能是由于上述原因。 但是没有任何东西阻止你创build一个具有引用成员的结构体/类的数组。

我相信有人会指出提到这一切的标准条款。

它不是固定的 – 编译器在如何实现一个个案的基础上有很大的自由。 所以在你的第二个例子中,它将j作为我的别名,没有其他需要。 当传递一个ref参数时,它也可以使用一个堆栈偏移量,同样没有开销。 但在其他情况下,它可以使用指针。