【问题标题】:Access to protected member through member-pointer: is it a hack?通过成员指针访问受保护的成员:是黑客攻击吗?
【发布时间】:2018-09-08 02:13:09
【问题描述】:

我们都知道从基类中指定protected 的成员只能从派生类自己的实例中访问。这是标准中的一项功能,在 Stack Overflow 上已多次讨论过:

但似乎可以用成员指针绕过这个限制,就像用户 chtz has shown me:

struct Base { protected: int value; };
struct Derived : Base
{
    void f(Base const& other)
    {
        //int n = other.value; // error: 'int Base::value' is protected within this context
        int n = other.*(&Derived::value); // ok??? why?
        (void) n;
    }
};

Live demo on coliru

为什么会出现这种情况,它是一个想要的功能还是在实施或标准的措辞中的某个地方出现的故障?


来自 cmets 的另一个问题是:if Derived::f is called with an actual Base,它是未定义的行为吗?

【问题讨论】:

  • 评论不用于扩展讨论;这个对话是moved to chat
  • @YvetteColomb 这是寻找问题解决方案/改进问题的集体努力。有没有办法把它们放回去?其中仍有一些信息可以改善接受的答案。
  • 他们都还在链接的聊天中。
  • 这拯救了我的一天。请注意the method f can be static,这有助于避免实际创建Derived 对象

标签: c++ language-lawyer protected access-specifier member-pointers


【解决方案1】:

由于访问控制 [class.access] 无法使用类成员访问 expr.ref (aclass.amember) 访问成员这一事实不会使该成员不可访问使用其他表达方式。

表达式 &Derived::value (whose type is int Base::*) 完全符合标准,它指定了 Base 的成员 value。那么表达式a_base.*p 其中p 是一个指向Base 成员的指针,a_baseBase 的一个实例也是standard compliant

因此,任何符合标准的编译器都应使表达式other.*(&Derived::value); 定义行为:访问other 的成员value

【讨论】:

    【解决方案2】:

    这是一个黑客吗?

    与使用reinterpret_cast 类似,这可能很危险,并且可能会导致难以发现的错误。但它的格式很好,毫无疑问它是否应该起作用。

    为了澄清类比:reinterpret_cast 的行为也在标准中准确指定,可以在没有任何 UB 的情况下使用。但是reinterpret_cast 绕过了类型系统,类型系统的存在是有原因的。同样,这个指向成员技巧的指针根据标准格式良好,但它绕过了成员的封装,并且封装(通常)存在是有原因的(我说通常是因为我认为程序员可以轻率地使用封装)。

    [是] 执行或标准措辞中的某个小故障吗?

    不,实现是正确的。这就是指定语言的工作方式。

    Derived 的成员函数显然可以访问&Derived::value,因为它是基的受保护成员。

    该操作的结果是指向Base 成员的指针。这可以应用于对Base 的引用。成员访问权限不适用于指向成员的指针:它仅适用于成员的名称。


    从 cmets 中出现了另一个问题:如果 Derived::f 是用一个实际的 Base 调用的,它是未定义的行为吗?

    不是 UB。 Base有会员。

    【讨论】:

      【解决方案3】:

      只是为了添加答案并放大一点我可以在您的字里行间读到的恐怖。如果您将访问说明符视为“法律”,并监督您阻止您做“坏事”,我认为您没有抓住重点。 publicprotectedprivateconst ... 都是 C++ 的巨大优势系统的一部分。没有它的语言可能有很多优点,但是当您构建大型系统时,这些东西是真正的资产。

      话虽如此:我认为可以绕过提供给您的几乎所有安全网是一件好事。只要您记住“可能”并不意味着“好”。这就是为什么它永远不应该是“容易的”。但对于其余的 - 这取决于你。你是建筑师。

      几年前我可以简单地做到这一点(它可能在某些环境中仍然有效):

      #define private public
      

      对于“敌对”外部头文件非常有帮助。好习惯?你怎么看?但有时您的选择是有限的。

      所以是的,你所展示的是系统中的一种违规行为。但是,嘿,是什么让您无法获取和分发该成员的公开参考资料?如果可怕的维护问题让您兴奋不已 - 无论如何,为什么不呢?

      【讨论】:

      • 我发现第一段相当混乱。如果不是一种“阻止你做'坏事'”的方法,还有什么访问说明符? Afaik 在正确的程序中,您可以将所有私有内容变为公开内容,它仍然是正确的
      • 我把所有这些东西都看作工具。类型安全、常量正确性、访问说明符——我们知道我们可以不用它们(选择你的语言),但我们可以选择使用这些工具。每个决定都有优点和缺点。你的意思是,任何有选择地、故意关闭的选择总是不好的?你用过演员表吗?
      【解决方案4】:

      基本上你正在做的是欺骗编译器,这应该可以工作。我总是看到这类问题,人们有时会得到不好的结果,有时它会起作用,这取决于它如何转换为汇编代码。

      我记得在一个整数上看到过一个带有const 关键字的案例,但是后来通过一些技巧,这个家伙能够更改值并成功地绕过编译器的意识。结果是:一个简单的数学运算的错误值。原因很简单:x86 中的汇编确实区分了常量和变量,因为某些指令的操作码中确实包含常量。因此,由于编译器相信它是一个常量,它会将它视为一个常量,并使用错误的 CPU 指令以优化的方式处理它,并且 baam,结果数字中有错误.

      换句话说:编译器将尝试强制执行它可以强制执行的所有规则,但您最终可能会欺骗它,并且您可能会或可能不会根据您正在尝试做的事情得到错误的结果,所以您最好只有在你知道自己在做什么的情况下才能做这些事情。

      在您的情况下,指针&Derived::value 可以从一个对象计算出从类的开头有多少字节。这基本上是编译器访问它的方式,所以,编译器:

      1. 没有发现任何权限问题,因为您在编译时通过derived 访问value
      2. 可以这样做,因为您在与derived 具有相同结构的对象中以字节为单位获取偏移量(当然,base)。

      所以,您没有违反任何规则。您成功绕过了编译规则。你不应该这样做,正是因为你附加的链接中描述的原因,因为它破坏了 OOP 封装,但是,如果你知道你在做什么......

      【讨论】:

      • 我怀疑 OP 是在询问实用性
      • 勇敢的努力。但这是一个很长的非答案。因为我认为 OP 想知道关于此的 正式 措辞是什么。
      • 我试图解释为什么会这样......希望它有所帮助。
      • 我认为问题在于您的答案对特定架构和/或实现有效,但 OP 在一般情况下想要一个,即对于 C++ 抽象机。
      • 发帖人深陷其中通常会更容易。投票的一个主要标准是“有用性”。虽然带有切线元素的答案可能很有用,但这个答案并非如此。正如 Rakete1111 所说,您专注于 OP 根本不关心的方面。
      猜你喜欢
      • 2016-04-14
      • 2017-10-15
      • 1970-01-01
      • 2021-01-09
      • 1970-01-01
      • 2017-08-01
      • 2016-10-01
      • 2013-08-06
      相关资源
      最近更新 更多