【问题标题】:Is private member hacking defined behaviour?私人成员黑客行为是否已定义?
【发布时间】:2011-02-19 12:51:59
【问题描述】:

我有以下课程:

class BritneySpears
{
  public:

    int getValue() { return m_value; };

  private:

    int m_value;
};

这是一个外部库(我无法更改)。我显然无法更改m_value 的值,只能读取它。即使从 BritneySpears 派生也行不通。

如果我定义以下类会怎样:

class AshtonKutcher
{
  public:

    int getValue() { return m_value; };

  public:

    int m_value;
};

然后做:

BritneySpears b;

// Here comes the ugly hack
AshtonKutcher* a = reinterpret_cast<AshtonKutcher*>(&b);
a->m_value = 17;

// Print out the value
std::cout << b.getValue() << std::endl;

我知道这是不好的做法。但只是出于好奇:这能保证有效吗?它是定义的行为吗?

额外问题:你曾经用过这么丑陋的黑客吗?

编辑:只是为了吓唬更少的人:我不打算在真实代码中真正做到这一点。我只是想知道 ;)

【问题讨论】:

  • 只要让BritneySpears::getValue()AshtonKutcher::getValue()简单地返回0就可以大大优化这段代码。
  • @ereOn:哦,我不够聪明,无法指出 C++ 代码中的错误。我指的是,如果这些类正确地模拟了实际的小甜甜布兰妮和阿什顿库彻,它们的值必须为 0。
  • 为什么要访问小甜甜的私处? (对不起。我真的很努力,但我无法抗拒。)
  • +1 用于在具有未定义行为的示例中使用 BritneySpears
  • +1 用于获取布兰妮的私人信息和“丑陋的黑客”在同一句话中。

标签: c++ casting private-members


【解决方案1】:

这是未定义的行为。每个访问限定符部分中的成员都保证按照它们出现的顺序排列,但是在访问限定符之间没有这样的保证。例如,如果编译器选择将所有私有成员放在所有公共成员之前,则上述两个类将具有不同的布局。

编辑: 重新审视这个旧答案,我意识到我错过了一个相当明显的点:每个结构定义都只有一个数据成员。成员函数的顺序无关紧要,因为它们对类的布局没有贡献。您可能会发现两个数据成员都可以保证在同一个位置,尽管我对标准的了解不够充分,无法确定。

但是!您不能在不相关的类型之间取消引用 reinterpret_casting 的结果。还是UB。至少,这是我对http://en.cppreference.com/w/cpp/language/reinterpret_cast 的阅读,这确实是一本粗糙的阅读。

【讨论】:

  • 那么如果BritneySpears 的每个成员都是私有的,而AshtonKutcher 的每个成员都是公共的呢?订单会得到保证吗?
  • @ereOn:仅当它们都在单个访问限定符部分中时。有两个部分,都是私有的,仍然会导致未指定的内存布局。
  • 我仍然想知道如何在不询问 SO 的情况下学习这一点。谢谢你的课! :D
  • @ereOn 对于这样的事情,唯一确定的方法是阅读和理解标准。
  • 即使每个类中的成员在排版上相同,如果使用不同的编译器选项或不同的编译器编译它们,您仍然无法保证实际的内存布局。
【解决方案2】:

通常应避免使用 reinterpret_cast,并且不能保证提供portable results

另外,为什么要更改私人会员?您可以将原始类包装在一个新类中(更喜欢组合而不是继承)并根据需要处理 getValue 方法。

【讨论】:

  • 好吧,我只是想知道。我不打算在任何实际代码中这样做;)
  • 由于方法getValue 不是虚拟的,因此您将无法通过扩展类来更改其在现有代码中的行为(基础级别的所有现有引用都将调用基础方法,并且该方法-- 不是真正的覆盖 -- 在派生类中永远不会被调用)
【解决方案3】:

这是未定义的行为,原因 Marcelo 指出。但有时在集成无法修改的外部代码时,您需要求助于此类事情。一种更简单的方法(以及同样未定义的行为)是:

#define private public
#include "BritneySpears.h"

【讨论】:

  • 我们可以拥有不带“私人”标签的私人会员,所以这可能行不通;)
  • @Nick D:虽然适用于结构(?)
  • @Nick:#define class struct。并不是说我会做那样的事情。
  • @Tyler,这是很好的信息。虽然,如果你像这样混合编译器,你不是很好吗?毕竟 C++ 没有定义的 ABI。
  • 你不能#define c++关键字,因此#define private public是不合法的c++
【解决方案4】:

@Marcelo 说得对:成员的顺序在不同的访问级别之间是未定义的。

但请考虑以下代码;在这里,AshtonKutcher 的布局与BritneySpears 完全相同:

class AshtonKutcher
{
  public:
    int getValue() { return m_value; };
    friend void setValue(AshtonKutcher&, int);

  private:
    int m_value;
};

void setValue(AshtonKutcher& ac, int value) {
    ac.m_Value = value;
}

我相信这实际上可能是有效的 C++。

【讨论】:

  • 好吧,我们不能保证编译器会以相同的顺序为两个不同的类对不同的部分进行排序。好吧,我看不出哪个实现会那样做,但仍然。
【解决方案5】:

您可能无法修改 BritneySpears 的库,但您应该可以修改 .h 头文件。如果是这样,你可以让AshtonKutcher成为BritneySpears的朋友:

class BritneySpears 
{
    friend class AshtonKutcher;
  public: 

    int getValue() { return m_value; }; 

  private: 

    int m_value; 
}; 

class AshtonKutcher 
{ 
  public: 

    int getValue(const BritneySpears & ref) { return ref.m_value; }; 
}; 

我真的不能容忍这种伎俩,我想我自己也没有尝试过,但它应该是合法的定义良好的 C++。

【讨论】:

  • 谁愿意和小甜甜布兰妮做朋友?
【解决方案6】:

您的代码存在问题,并在答案中突出显示。问题来自对值的排序。

但是你快到了:

class AshtonKutcher
{
public:

  int getValue() const { return m_value; }
  int& getValue() { return m_value; }

private:
  int m_value;
};

现在,您拥有完全相同的布局,因为您拥有相同的属性、以相同的顺序声明并具有相同的访问权限……而且两个对象都没有虚拟表。

因此,诀窍不是更改访问级别,而是添加一个方法:)

当然,除非我错过了什么。

我说这是一场维护噩梦吗?

【讨论】:

    猜你喜欢
    • 2013-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-06
    • 2018-01-03
    • 2011-07-25
    • 2011-06-08
    • 1970-01-01
    相关资源
    最近更新 更多