【问题标题】:Is always the address of a reference equal to the address of origin?引用的地址总是等于原始地址吗?
【发布时间】:2017-08-22 15:26:39
【问题描述】:

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

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

【问题讨论】:

  • 对于它们可能是什么,您还有其他想法吗?
  • 参考文献没有地址。对象有地址。一个对象只有一个地址。引用指的是它被初始化的对象。不要复制它。
  • @n.m.:参考文献有时有地址,只是很难找到它们。特别是,&amp;r 不使用引用 r 的地址,就像 r + 1 进行指针运算一样。间接在任何操作之前自动完成,因此&amp;r 类似于&amp;(*p)
  • 你必须考虑到甚至不能保证&amp;foofoo 的地址,因为operator&amp; 可以实现返回它喜欢的任何东西。是否有意义当然是一个不同的问题;)

标签: c++ pointers reference lvalue


【解决方案1】:

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

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

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

是的,这是有保证的(除非地址操作符被一个愚蠢的实现重载)。

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


现在,可以重载 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

【讨论】:

  • @tobi303 扩大了答案以涵盖该案例。
  • 为了完整起见,您可以提及std::adressof,它规避了这种邪恶(afaik 正是为此目的而引入的:确保您得到的确实是地址)
【解决方案2】:

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

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

因此,如果您有类似int i_var = 10; int&amp; i_ref = i; 的东西,无论您使用它形成什么可执行表达式,任何提及i_ref 的内容实际上都意味着i_var

此外,有些人发现将引用视为“自解引用指针”会有所帮助。所以想象你有一个指针int* p,但每次你把它称为p你实际上是指*p。例如,p = 10 表示*p = 10&amp;p 表示&amp;(*p)——p 也指向的int 的地址。这就是逻辑引用的工作原理。

它也适用于您的代码。只要你有 Point &amp;p = plist[2];(当您调用 func(plist[2]) 时发生)然后 pplist[2] 开始引用同一事物 - 一些由 plist 中的 2 的索引存储的 Point 对象。所以现在&amp;plist[2]&amp;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&lt;decltype(a), decltype(b)&gt; 在编译时被评估,所以我不认为它是一个“可执行表达式”。

体能水平
请注意,直到现在我没有说 引用的地址被引用的变量的地址是相同的。为什么?因为如果你从逻辑上思考 - 他们不是

无论您喜欢与否,都必须以某种方式实现引用。我相信,在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)

【讨论】:

    【解决方案3】:

    当引用的类型和用来初始化它的变量的类型不同时,引用的地址可以和初始化变量的地址不同。

    例如

    #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 不同,尽管这两个引用是从同一个变量初始化的。

    【讨论】:

      【解决方案4】:

      是否总是保证这些地址在任何情况下都是相等的?

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

      【讨论】:

      • 让我们忽略&amp; 运算符。您能否为此声明提供 C++ 参考资料中的任何链接?谢谢。
      【解决方案5】:

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

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

      【讨论】:

      • 在最后几句话中,您的意思是,&amp; 运算符的结果将是相同的 - 引用地址和“引用地址”不同。
      • 让我们忽略&amp; 运算符。您能否为此声明提供任何参考?谢谢。
      • @WindyFields - 我的意思是即使 &ref variable != &referent 在内部但对程序员 &ref variable == &referent 遵守标准。
      猜你喜欢
      • 1970-01-01
      • 2012-10-21
      • 2018-06-08
      • 1970-01-01
      • 1970-01-01
      • 2015-04-25
      • 1970-01-01
      • 2011-10-03
      • 2011-02-23
      相关资源
      最近更新 更多