【问题标题】:Accessing the address of a member data through qualified name yields error通过限定名访问成员数据的地址会产生错误
【发布时间】:2021-04-16 00:06:55
【问题描述】:

我们知道,派生类publicprotected 成员的直接或间接基类在派生类中可用。

所以我有这个例子:

struct Foo{int x_ = 10;};
struct Bar : Foo{};
struct FooBar : Bar{
    void f()const{
        std::cout << &x_ << " : " << x_ << '\n';
        std::cout << &Foo::x_ << " : " << Foo::x_ << '\n';
        std::cout << &Bar::x_ << " : " << Bar::x_ << '\n';
        std::cout << &FooBar::x_ << " : " << FooBar::x_ << '\n';
    }
};



int main(){
    FooBar fb{};
    fb.f();
}
  • 当我编译并运行程序时,我得到了输出:

      0x7ffc2f7ca878 : 10
      1 : 10
      1 : 10
      1 : 10
    

那么为什么通过完全限定名称访问成员数据A::x_ 的地址会产生“无效”地址?但是直接访问它(不合格的查找)是可以的。

  • 我的程序是否处于未定义行为?

  • 我已经使用 GCC 和 CLANG 编译了我的程序。

【问题讨论】:

  • 您能否明确说明您对哪些输出感到困惑?另外,A::x_ 是什么?
  • @cigien 为什么我从 &Foo::x_ 、 &Bar::x_ 和 &Bar::x_ 得到 1 : 1?
  • 不相关,std::cout &lt;&lt; &amp;Foo::x_ &lt;&lt; " : " &lt;&lt; &amp;Foo::x_ &lt;&lt; '\n'; 中的第二个&amp;Foo::x_ 应该是Foo::x_吗?

标签: c++ memory-address


【解决方案1】:

首先,您很可能不打算在每个 std::cout 语句中重复 &amp; 运算符,因此我删除了每个重复的实例,以打印成员的值而不是其地址。

现在看这个:

#include <iostream>

struct Foo{int x_ = 10;};
struct Bar : Foo{};
struct FooBar : Bar
{
  void f()const
  {
    std::cout << &x_ << " : " << x_ << '\n';
    std::cout << &(Foo::x_) << " : " << Foo::x_ << '\n';
    std::cout << &(Bar::x_) << " : " << Bar::x_ << '\n';
    std::cout << &(FooBar::x_) << " : " << FooBar::x_ << '\n';
  }
};

int main(){
  FooBar fb{};
  fb.f();
}

输出:

0x7fffe136c594:10
0x7fffe136c594 : 10
0x7fffe136c594 : 10
0x7fffe136c594:10

一切正常!我所做的更改是将Foo::x_ 之类的表达式括在括号中。为什么?因为&amp;Foo::x_ 使用内置地址操作符,请参阅cppreference.com 中的Member access operators。它的值是一个指向数据成员的指针,它不是一个普通的指针,因为它需要绑定到一个对象才能返回一个地址。另请参阅Similar stackoverflow question 了解更多说明。

编辑

您可能想知道为什么std::cout 为指向成员的指针显示1。这是因为隐式转换为bool

布尔转换
整型、浮点型、无范围枚举、指针和指向成员的指针类型的纯右值可以转换为 bool 类型的纯右值。

Quotation source

转换后的值当然是true,因为你的指针是有效的,只有nullptr(和0)被转换为false。根据其他规则,true 被转换为 1。Q.E.D.

【讨论】:

  • 是的,当然!在那里两次使用引用运算符是一个错误。
  • 我真的很感激!现在很清楚了。谢谢!
  • 坦率地说,我不知道。我猜&amp; :: 是一个单独的运算符,解析它优先于::。但是,我从未在 C++ 运算符列表或运算符优先级列表中看到 &amp; ::。如果你写++x,语法表明这是一个++ 运算符,而不是两个+ 运算符。这里应该是一样的。语法胜过运算符优先级表。
  • 另外,看看指针数据成员是如何实现的也很有趣。在 gcc 中,它们似乎只是它们指向的结构成员的偏移量,因此编译器将它们应用于对象有一个简单的任务:获取对象的地址并将其增加这个偏移量。
  • 我想象它是这样的。如果编译器解析&amp;X,它会检查X 是对象还是可寻址的左值。如果是,则结束解析该令牌,并假定&amp; 是地址运算符。否则,它会检查 X 是否代表类或结构名称。如果是,它会进入“成员地址”模式并期望:: 后跟X 的成员。因此,&amp;Bar::x_ 将形成一个解析标记。相反,在&amp;(Bar::x_) 中有两个令牌,其中一个是(Bar::x_),另一个是&amp;,是第一个的结果。
【解决方案2】:

我的程序是否处于未定义行为?

没有。行为定义明确。

那么为什么通过完全限定名称访问成员数据 A::x_ 的地址会产生“无效”地址?

您没有使用完全限定的名称。完全限定名称始终从全局命名空间开始。例如,::Foo::x_ 将是一个完全限定名称,而 Foo::x_ 不是。

您也没有“访问”成员(插入x_ 时除外)。当您对成员名称应用一元运算符&amp; 时,结果是指向数据成员的指针。对于&amp;Foo::x_,类型将是int Foo::*,即指向Foo 类型int 的成员的指针。

此外,1 不一定是“无效”地址,但您观察到的根本不是地址。字符流没有接受指向数据成员的指针的流插入运算符。然而,它们确实有一个接受 bool 的重载,并且所有指向数据成员的指针都可以隐式转换为 bool,因此这种重载是重载解决方案的有效候选者。

由于指向数据成员的指针不为空,因此转换后的值为真。将 true 插入字符流时,输出为 1。

【讨论】:

  • 非常感谢您提供如此有用的解释。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多