始终是引用的地址等于原点的地址吗?

时间:2017-08-22 15:26:39

标签: c++ pointers reference lvalue

这看起来是一个非常基本的话题,但对我来说非常重要。

以下示例显示引用变量的地址等于原始变量的地址。我知道这是我们对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

5 个答案:

答案 0 :(得分:4)

  

引用的地址是否始终等于原始地址?

对象有地址。引用不是对象,并且(必然)没有地址。当address-of运算符应用于引用时,结果是引用对象的地址。

  

是否始终保证这些地址在任何情况下都相同?

是的,它是有保证的(除非运营商的地址超载了愚蠢的实现)。

引用引用的对象与该引用引用的对象相同。对象具有与该对象相同的地址...因为它是该对象:)

现在,可以重载addressof运算符,使其不再返回该对象的实际地址。在这种情况下,这两个调用可能会导致不同的地址。演示:

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

答案 1 :(得分:2)

不幸的是,许多人混淆了C ++引用的逻辑和物理含义。

逻辑级别
我自己找到了一个关于C ++引用的重要事项:
一旦我初始化了一个引用,它就变成 unpronounceable 。我所说的“不可发音”是指总是,当您在运行时可执行代码中命名引用时 - 您自动获取它所引用的变量,因此没有你可以触摸参考本身的方式。 Reference只是变量的替代名称(别名)。

因此,如果您使用int i_var = 10; int& i_ref = i;创建了可执行表达式之类的内容,那么提及i_ref实际上就意味着i_var

此外,有些人认为将引用视为“自引用指针”是有帮助的。因此,假设您有一个指针int* p,但每次将其称为p时,您实际上都是*p。例如,p = 10表示*p = 10&p表示&(*p) - int的地址p。这就是逻辑引用的工作原理。

它也适用于您的代码。只要你有 Point &p = plist[2];(当您调用func(plist[2])时发生),然后pplist[2]开始引用相同的内容 - Point对象存储的索引为plist {1}}。现在,&plist[2]&p 绝对相等。

输入系统级别
如果您注意到,我使用术语“运行时可执行代码”或“可执行表达式”。让我澄清一下 编译器实际上知道ab

之间的区别
int a = 0;
int& b = a;

std::cout << std::boolalpha 
          << std::is_same_v<decltype(a), decltype(b)>; // -> false

如您所见,ab类型不同。但是,std::is_same_v<decltype(a), decltype(b)>在编译时被评估,因此我不认为它是“可执行表达式”。

物理等级
请注意,到目前为止,我没有说,参考地址和被引用的变量的地址是相同的。为什么?因为如果你从逻辑上思考 - 他们不是

无论您是否喜欢,都必须以某种方式实施参考。我相信,在使用i_vari_ref编译器的示例中,只需将所有i_ref替换为i_var,任何“引用”的物理表示都将永远不存在。另一方面,如果将引用存储在类中,则很可能使用指针实现。
虽然,一个实现是依赖于编译器的,如果引用实际上指针下的指针,很明显这个指针的地址和它指向的对象的地址是不同的

但是,你为什么要关心?你永远不会知道参考地址!在任何可执行表达式中,当你说i_ref暗示i_var时,请记住?:)

好的,如果你真的好奇“参考的地址是什么”,有一种情况可以解决它 - 当参考是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)

答案 2 :(得分:1)

如果引用的类型和用于对其进行初始化的变量的类型不同,则引用的地址可以与初始化变量的地址不同。

例如

#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的地址不同。

答案 3 :(得分:0)

  

是否始终保证这些地址在任何情况下都相同?

是的,您传递的是该对象的引用,因此没有复制(如果这是问题的背景),您打印的内容正是您调用该函数时原始参数的地址。

答案 4 :(得分:0)

在任何情况下,始终保证这些地址是平等的吗?

根据C ++标准,未指定引用是否需要存储。另一方面,它还说,获取引用的地址会为您提供引用的地址。因此,即使编译器选择在内部使用单独的存储作为引用,也可以保证程序员的地址是相同的。