(传统的,之前的 c++11 左值-)引用如何在 C++ 中工作
How do (traditional, prior c++11 lvalue-) references work in C++
指针很容易。有一些内存保存着一个地址。要获得(有意义的)值,取消引用 returns 地址指向的内存中包含的值。
引用以某种方式做一些类似的事情:它们将 "link" 保存到一个临时对象。但是当我分配或使用引用时会发生什么?
#include <iostream>
using namespace std;
int one(){
return 1;
}
int main()
{
const int &rone = one();
cout << &rone << endl;
return 0;
}
为什么这样做?这个地址是临时对象的地址吗?
sh-4.2# g++ -std=c++11 -o main *.cpp
sh-4.2# main
0x7fff9d2a4e74
这个地址指向哪里?如果它指向神奇的临时对象,为什么我不能只执行以下操作。 (我很清楚它是右值并且 &
只接受左值,但为什么呢?)
int main()
{
cout << &one() << endl;
return 0;
}
我的问题超出了 c++ 的编程方面。它在技术方向上更多地意味着 C++ 在内部是如何工作的。我主要尝试在移动语义的上下文中理解右值引用,这需要理解那些传统引用(我几乎从不使用)。
您可以将临时文件绑定到 rone
,因为编译器知道您不会修改它。读取临时值是安全的,写入临时值则不然。
如果删除 const
,代码将不再编译。
另一种理解引用的方式是别名。绑定引用为对象提供了另一个名称。在 C++11 之前,这两个片段完全相同:
int a = 5;
int &b = a;
对比
int b = 5;
int &a = b;
此后,a
和b
都是同一个对象的名称。 (C++11 引入了一种不对称性,因为 decltype
将在返回的类型中包含引用)。
这与您的第一个示例没有任何不同。您将 rone
绑定为临时对象的名称,语言规则说这也会延长临时对象的生命周期。
在你的第二个例子中没有引用。地址运算符是与用于声明引用的符号不同的语言元素,尽管它们具有相同的拼写。这是非法的,因为语言定义说地址运算符不能应用于创建临时对象的表达式。
变量(对象)和引用是"names"(指针也是变量所以同样适用)。程序员使用它们来识别程序中的某些对象。请注意,引用不仅引用临时对象。他们可以引用任何有效的对象。
其他东西(即右值)没有名称,也没有指定的可访问性 "location" 它们的处理是编译器的工作。在 x86 中的示例中(取决于调用约定),小的 return 值通常通过某些处理器寄存器 returned。
考虑:
int one() { return 1; }
int main()
{
one();
return 0;
}
return 值在这里消失。 (如果函数可见,编译器可能根本不会调用该函数。)
如果您可以获取 return 值的地址,则它必须存储在某处(可以使用有效地址访问的位置)。
因此,为了能够获取由 one()
编辑的值 return 的地址,必须从寄存器 EAX 复制该值(当使用 cdecl 时它通常驻留的位置)到某个位置 "save" 由 c++ 程序(mer)访问。
如果您确实可以访问临时 "directly" 的地址会怎样?
int const * p = &one(); // would this return "EAX"?
int c = *(p + 1); // ??²
你会读第二行的寄存器EBX吗?或者 L1 或 L2 或 或 中的任何内存位置?这样的事情没有意义。
C++ 中唯一可访问的位置是堆栈内存和空闲存储区 ("heap"),如果您为 return 类型创建变量或常量引用,将使用其中之一。
int main()
{
int const a = one();
return 0;
}
与这里的第一个示例相比,情况并没有太大变化:a
分配了名称和内存,而函数的 return 值(仍然)具有(并且将始终具有)不是。 return 的值是 "computed" 到 EAX 中,a
通过将寄存器值复制到 a
的内存位置来使用该整数初始化。
int const & r = one();
这里差别不大。 return 值存储在 persistent/accessible 内存位置,现在您可以使用
获取它的地址
int const * p = &r;
这个地址指向哪里?
我不确定,但我不认为该标准指定了长时间临时文件的任何位置(如果我错了请纠正我)。您只需要知道:r
指的是一个有效对象,&r
是该对象的地址。 (您可能可以将地址与附近的一些堆栈变量地址进行比较,以确定它是否也可能在堆栈上。)
r
和 a
之间有一点不同:值(存储在 r
所指的位置)不一定在堆栈上(尽管我认为它会最终在堆栈上),而上面的 a
肯定在堆栈上。
另一个不同之处在于对象。 (至少在理论上,尽管允许 c++ 编译器以某种方式进行优化,但以下段落中的 a
和 b
在某种程度上是等效的)。
struct X { /*stuff*/ };
X foo() { return X{ /*.args.*/ }; }
// ...
X const a = foo(); // A
X const & b = foo(); // B
在行 B
中,b
是 X
类型常量对象的名称。 (在幕后,编译器会提前为对象选择一些位置,然后 return foo
直接进入该内存以避免复制它。)
在行 A
中,a
将(在堆栈上并)从临时 return 由 foo
通过它的复制构造函数初始化。 (可以省略副本,优化编译器很可能会将临时文件直接构造为 a。)
变量名和引用是人类识别标记。变量/对象是可能存在多个引用的单个实例。
指针很容易。有一些内存保存着一个地址。要获得(有意义的)值,取消引用 returns 地址指向的内存中包含的值。
引用以某种方式做一些类似的事情:它们将 "link" 保存到一个临时对象。但是当我分配或使用引用时会发生什么?
#include <iostream>
using namespace std;
int one(){
return 1;
}
int main()
{
const int &rone = one();
cout << &rone << endl;
return 0;
}
为什么这样做?这个地址是临时对象的地址吗?
sh-4.2# g++ -std=c++11 -o main *.cpp
sh-4.2# main
0x7fff9d2a4e74
这个地址指向哪里?如果它指向神奇的临时对象,为什么我不能只执行以下操作。 (我很清楚它是右值并且 &
只接受左值,但为什么呢?)
int main()
{
cout << &one() << endl;
return 0;
}
我的问题超出了 c++ 的编程方面。它在技术方向上更多地意味着 C++ 在内部是如何工作的。我主要尝试在移动语义的上下文中理解右值引用,这需要理解那些传统引用(我几乎从不使用)。
您可以将临时文件绑定到 rone
,因为编译器知道您不会修改它。读取临时值是安全的,写入临时值则不然。
如果删除 const
,代码将不再编译。
另一种理解引用的方式是别名。绑定引用为对象提供了另一个名称。在 C++11 之前,这两个片段完全相同:
int a = 5;
int &b = a;
对比
int b = 5;
int &a = b;
此后,a
和b
都是同一个对象的名称。 (C++11 引入了一种不对称性,因为 decltype
将在返回的类型中包含引用)。
这与您的第一个示例没有任何不同。您将 rone
绑定为临时对象的名称,语言规则说这也会延长临时对象的生命周期。
在你的第二个例子中没有引用。地址运算符是与用于声明引用的符号不同的语言元素,尽管它们具有相同的拼写。这是非法的,因为语言定义说地址运算符不能应用于创建临时对象的表达式。
变量(对象)和引用是"names"(指针也是变量所以同样适用)。程序员使用它们来识别程序中的某些对象。请注意,引用不仅引用临时对象。他们可以引用任何有效的对象。
其他东西(即右值)没有名称,也没有指定的可访问性 "location" 它们的处理是编译器的工作。在 x86 中的示例中(取决于调用约定),小的 return 值通常通过某些处理器寄存器 returned。
考虑:
int one() { return 1; }
int main()
{
one();
return 0;
}
return 值在这里消失。 (如果函数可见,编译器可能根本不会调用该函数。) 如果您可以获取 return 值的地址,则它必须存储在某处(可以使用有效地址访问的位置)。
因此,为了能够获取由 one()
编辑的值 return 的地址,必须从寄存器 EAX 复制该值(当使用 cdecl 时它通常驻留的位置)到某个位置 "save" 由 c++ 程序(mer)访问。
如果您确实可以访问临时 "directly" 的地址会怎样?
int const * p = &one(); // would this return "EAX"?
int c = *(p + 1); // ??²
你会读第二行的寄存器EBX吗?或者 L1 或 L2 或 或 中的任何内存位置?这样的事情没有意义。
C++ 中唯一可访问的位置是堆栈内存和空闲存储区 ("heap"),如果您为 return 类型创建变量或常量引用,将使用其中之一。
int main()
{
int const a = one();
return 0;
}
与这里的第一个示例相比,情况并没有太大变化:a
分配了名称和内存,而函数的 return 值(仍然)具有(并且将始终具有)不是。 return 的值是 "computed" 到 EAX 中,a
通过将寄存器值复制到 a
的内存位置来使用该整数初始化。
int const & r = one();
这里差别不大。 return 值存储在 persistent/accessible 内存位置,现在您可以使用
获取它的地址int const * p = &r;
这个地址指向哪里?
我不确定,但我不认为该标准指定了长时间临时文件的任何位置(如果我错了请纠正我)。您只需要知道:r
指的是一个有效对象,&r
是该对象的地址。 (您可能可以将地址与附近的一些堆栈变量地址进行比较,以确定它是否也可能在堆栈上。)
r
和 a
之间有一点不同:值(存储在 r
所指的位置)不一定在堆栈上(尽管我认为它会最终在堆栈上),而上面的 a
肯定在堆栈上。
另一个不同之处在于对象。 (至少在理论上,尽管允许 c++ 编译器以某种方式进行优化,但以下段落中的 a
和 b
在某种程度上是等效的)。
struct X { /*stuff*/ };
X foo() { return X{ /*.args.*/ }; }
// ...
X const a = foo(); // A
X const & b = foo(); // B
在行 B
中,b
是 X
类型常量对象的名称。 (在幕后,编译器会提前为对象选择一些位置,然后 return foo
直接进入该内存以避免复制它。)
在行 A
中,a
将(在堆栈上并)从临时 return 由 foo
通过它的复制构造函数初始化。 (可以省略副本,优化编译器很可能会将临时文件直接构造为 a。)
变量名和引用是人类识别标记。变量/对象是可能存在多个引用的单个实例。