引用地址是否总是等于原始地址?
Is always the address of a reference equal to the address of origin?
这看起来是一个非常基础的主题,但对我来说非常重要。
下面的例子表明引用变量的地址等于原始变量的地址。我知道这是我们可以从 C/C++ 的概念中得到的期望。但是,是否总能保证这些地址在任何情况下都相等?
#include <iostream>
#include <vector>
#include <string>
class Point
{
public:
double x,y;
};
void func(Point &p)
{
std::cout<<"&p="<<&p<<std::endl;
}
int main()
{
std::vector<Point> plist;
plist.push_back({1.0,4.0});
plist.push_back({10.0,20.0});
plist.push_back({3.0,5.0});
plist.push_back({7.0,0.4});
std::cout<<"&plist[2]="<<&plist[2]<<std::endl;
func(plist[2]);
return 0;
}
结果:
&plist[2]=0x119bc90
&p=0x119bc90
Is always the address of a reference equal to the address of origin?
对象有地址。引用不是对象,并且(不一定)有地址。当 address-of 运算符应用于引用时,结果是引用对象的地址。
is it always guaranteed that these addresses are equal under any circumstance?
是的,这是有保证的(除非 address-of 运算符被愚蠢的实现重载)。
引用引用的对象与该引用引用的对象是同一个对象。一个对象的地址与该对象的地址相同...因为它就是那个对象:)
现在,可以重载 addressof 运算符,使其不再 returns 该对象的实际地址。在这种情况下,这两次调用可能会产生不同的地址。演示:
struct evil {
static evil silly;
static bool insane;
evil* operator&() {
evil* bonkers = insane ? std::addressof(silly) : this;
insane = !insane; // does this mean it is no longer insane?
return bonkers;
}
};
bool evil::insane = true;
foo evil::silly;
int main() {
evil e;
evil& ref = e;
std::cout << &e << '\n';
std::cout << &ref << '\n';
std::cout << &ref << '\n';
}
可能的输出:
0x7ffffbeef42d
0x600dd1
0x7ffffbeef42d
is it always guaranteed that these addresses are equal under any circumstance?
是的,你正在传递那个对象的引用,所以没有复制,(如果这是问题的背景),你正在打印的正是你调用时原始参数的地址函数。
不幸的是,许多人混淆了 C++ 引用的逻辑意义和物理意义。
逻辑级别
关于 C++ 引用,我自己发现了一件至关重要的事情:
一旦我初始化了一个引用,它就会变成 unpronounceable .我所说的 "unpronounceable" 的意思是 总是 当你在 运行时可执行代码 中命名一个引用时 - 你会自动获取变量它指的是,所以你无法触摸引用本身。引用只是变量的替代名称(别名)。
所以如果你有类似 int i_var = 10; int& i_ref = i;
的东西,无论你用它形成什么 可执行表达式 ,任何提及 i_ref
实际上都意味着 i_var
.
此外,有些人认为将引用视为 "self-dereferencing pointer" 很有帮助。假设您有一个指针 int* p
,但每次您将其称为 p
时,您实际上指的是 *p
。例如,p = 10
表示 *p = 10
而 &p
表示 &(*p)
- int
的地址也指向 p
。这就是引用在逻辑上的工作方式。
它也适用于您的代码。只要你有
Point &p = plist[2];
(当你调用 func(plist[2])
时发生)然后 p
和 plist[2]
开始引用同一事物 - 一些 Point
对象存储在 plist
。所以现在 &plist[2]
和 &p
绝对 相等。
输入系统级别
如果您注意到了,我使用了术语 "runtime executable code" 或 "executable expression"。让我澄清一下。
编译器其实知道a
和b
的区别:
int a = 0;
int& b = a;
std::cout << std::boolalpha
<< std::is_same_v<decltype(a), decltype(b)>; // -> false
如您所见,a
和 b
类型不同。但是,std::is_same_v<decltype(a), decltype(b)>
在编译时得到评估,所以我不认为它是 "executable expression"。
物理水平
注意,直到现在我没有说引用的地址和被引用的变量的地址[=92] =] 是一样的。为什么?因为如果你从逻辑上思考——它们不是。
引用必须以某种方式实现,无论您喜欢与否。我相信,在带有 i_var
和 i_ref
的示例中,编译器将简单地将所有 i_ref
替换为 i_var
,并且 "reference" 的任何物理表示将永远不存在。另一方面,如果您将引用存储在 class 中,则很可能使用指针来实现。
尽管实现依赖于编译器,如果引用实际上是引擎盖下的指针,很明显这个指针的地址和对象的地址是指向不同。
但是,你为什么要关心呢?您永远不会知道参考地址!在任何 可执行表达式 中,当你说 i_ref
时你暗示 i_var
,还记得吗?:)
好吧,如果你真的很好奇"what is the address of a reference",有一种情况你可以搞清楚——当引用是a member of a class:
int main()
{
int var = 10;
int& real_ref = var;
struct { int& ref; } fake_ref = { var };
std::cout << &var << std::endl; // address of var
std::cout << &real_ref << std::endl; // still address of var
std::cout << &fake_ref << std::endl; // address of reference to var
std::cout << sizeof var << std::endl; // size of var
std::cout << sizeof real_ref << std::endl; // still size of var
std::cout << sizeof fake_ref << std::endl; // size of reference to var
return 0;
}
x64 编译器的输出:
000000A9272FFBA4 <- same
000000A9272FFBA4 <- same
000000A9272FFBC0 <- different
4 <- same
4 <- same
8 <- different (8 on 64 bit and 4 on 32 bit compiler)
是否总能保证这些地址在任何情况下都相等?
根据 C++ 标准,未指定引用是否需要存储。另一方面,它还说获取引用的地址可以为您提供引用地址。因此,即使编译器选择在内部为引用单独存储而不是引用,也可以向程序员保证它们的地址是相同的。
如果引用的类型和用于初始化它的变量的类型不同,则引用的地址可以与初始化变量的地址不同。
例如
#include <stdio.h>
struct Class1
{
int x;
};
struct Class2
{
int p;
};
struct Class12 : Class1, Class2 {};
int main(int argc, char* argv[])
{
Class12 p;
Class1& r1 = p;
Class2& r2 = p;
printf("p=%p, r1=%p, r2=%p", &p, &r1, &r2);
}
当运行时,输出为:
p=0x7ffefcfd1a20, r1=0x7ffefcfd1a20, r2=0x7ffefcfd1a24
r1 的地址与 r2 不同,尽管这两个引用是从同一个变量初始化的。
这看起来是一个非常基础的主题,但对我来说非常重要。
下面的例子表明引用变量的地址等于原始变量的地址。我知道这是我们可以从 C/C++ 的概念中得到的期望。但是,是否总能保证这些地址在任何情况下都相等?
#include <iostream>
#include <vector>
#include <string>
class Point
{
public:
double x,y;
};
void func(Point &p)
{
std::cout<<"&p="<<&p<<std::endl;
}
int main()
{
std::vector<Point> plist;
plist.push_back({1.0,4.0});
plist.push_back({10.0,20.0});
plist.push_back({3.0,5.0});
plist.push_back({7.0,0.4});
std::cout<<"&plist[2]="<<&plist[2]<<std::endl;
func(plist[2]);
return 0;
}
结果:
&plist[2]=0x119bc90
&p=0x119bc90
Is always the address of a reference equal to the address of origin?
对象有地址。引用不是对象,并且(不一定)有地址。当 address-of 运算符应用于引用时,结果是引用对象的地址。
is it always guaranteed that these addresses are equal under any circumstance?
是的,这是有保证的(除非 address-of 运算符被愚蠢的实现重载)。
引用引用的对象与该引用引用的对象是同一个对象。一个对象的地址与该对象的地址相同...因为它就是那个对象:)
现在,可以重载 addressof 运算符,使其不再 returns 该对象的实际地址。在这种情况下,这两次调用可能会产生不同的地址。演示:
struct evil {
static evil silly;
static bool insane;
evil* operator&() {
evil* bonkers = insane ? std::addressof(silly) : this;
insane = !insane; // does this mean it is no longer insane?
return bonkers;
}
};
bool evil::insane = true;
foo evil::silly;
int main() {
evil e;
evil& ref = e;
std::cout << &e << '\n';
std::cout << &ref << '\n';
std::cout << &ref << '\n';
}
可能的输出:
0x7ffffbeef42d
0x600dd1
0x7ffffbeef42d
is it always guaranteed that these addresses are equal under any circumstance?
是的,你正在传递那个对象的引用,所以没有复制,(如果这是问题的背景),你正在打印的正是你调用时原始参数的地址函数。
不幸的是,许多人混淆了 C++ 引用的逻辑意义和物理意义。
逻辑级别
关于 C++ 引用,我自己发现了一件至关重要的事情:
一旦我初始化了一个引用,它就会变成 unpronounceable .我所说的 "unpronounceable" 的意思是 总是 当你在 运行时可执行代码 中命名一个引用时 - 你会自动获取变量它指的是,所以你无法触摸引用本身。引用只是变量的替代名称(别名)。
所以如果你有类似 int i_var = 10; int& i_ref = i;
的东西,无论你用它形成什么 可执行表达式 ,任何提及 i_ref
实际上都意味着 i_var
.
此外,有些人认为将引用视为 "self-dereferencing pointer" 很有帮助。假设您有一个指针 int* p
,但每次您将其称为 p
时,您实际上指的是 *p
。例如,p = 10
表示 *p = 10
而 &p
表示 &(*p)
- int
的地址也指向 p
。这就是引用在逻辑上的工作方式。
它也适用于您的代码。只要你有
Point &p = plist[2];
(当你调用 func(plist[2])
时发生)然后 p
和 plist[2]
开始引用同一事物 - 一些 Point
对象存储在 plist
。所以现在 &plist[2]
和 &p
绝对 相等。
输入系统级别
如果您注意到了,我使用了术语 "runtime executable code" 或 "executable expression"。让我澄清一下。
编译器其实知道a
和b
的区别:
int a = 0;
int& b = a;
std::cout << std::boolalpha
<< std::is_same_v<decltype(a), decltype(b)>; // -> false
如您所见,a
和 b
类型不同。但是,std::is_same_v<decltype(a), decltype(b)>
在编译时得到评估,所以我不认为它是 "executable expression"。
物理水平
注意,直到现在我没有说引用的地址和被引用的变量的地址[=92] =] 是一样的。为什么?因为如果你从逻辑上思考——它们不是。
引用必须以某种方式实现,无论您喜欢与否。我相信,在带有 i_var
和 i_ref
的示例中,编译器将简单地将所有 i_ref
替换为 i_var
,并且 "reference" 的任何物理表示将永远不存在。另一方面,如果您将引用存储在 class 中,则很可能使用指针来实现。
尽管实现依赖于编译器,如果引用实际上是引擎盖下的指针,很明显这个指针的地址和对象的地址是指向不同。
但是,你为什么要关心呢?您永远不会知道参考地址!在任何 可执行表达式 中,当你说 i_ref
时你暗示 i_var
,还记得吗?:)
好吧,如果你真的很好奇"what is the address of a reference",有一种情况你可以搞清楚——当引用是a member of a class:
int main()
{
int var = 10;
int& real_ref = var;
struct { int& ref; } fake_ref = { var };
std::cout << &var << std::endl; // address of var
std::cout << &real_ref << std::endl; // still address of var
std::cout << &fake_ref << std::endl; // address of reference to var
std::cout << sizeof var << std::endl; // size of var
std::cout << sizeof real_ref << std::endl; // still size of var
std::cout << sizeof fake_ref << std::endl; // size of reference to var
return 0;
}
x64 编译器的输出:
000000A9272FFBA4 <- same
000000A9272FFBA4 <- same
000000A9272FFBC0 <- different
4 <- same
4 <- same
8 <- different (8 on 64 bit and 4 on 32 bit compiler)
是否总能保证这些地址在任何情况下都相等?
根据 C++ 标准,未指定引用是否需要存储。另一方面,它还说获取引用的地址可以为您提供引用地址。因此,即使编译器选择在内部为引用单独存储而不是引用,也可以向程序员保证它们的地址是相同的。
如果引用的类型和用于初始化它的变量的类型不同,则引用的地址可以与初始化变量的地址不同。
例如
#include <stdio.h>
struct Class1
{
int x;
};
struct Class2
{
int p;
};
struct Class12 : Class1, Class2 {};
int main(int argc, char* argv[])
{
Class12 p;
Class1& r1 = p;
Class2& r2 = p;
printf("p=%p, r1=%p, r2=%p", &p, &r1, &r2);
}
当运行时,输出为:
p=0x7ffefcfd1a20, r1=0x7ffefcfd1a20, r2=0x7ffefcfd1a24
r1 的地址与 r2 不同,尽管这两个引用是从同一个变量初始化的。