【问题标题】:c++ dangerous casting codeC++ 危险的铸造代码
【发布时间】:2023-04-07 14:52:02
【问题描述】:

我很确定这是危险代码。但是,我想看看是否有人知道 究竟会出什么问题。

假设我有这样的类结构:

class A {
protected:
  int a;
public:
  A() { a = 0; }        
  int getA() { return a; }
  void setA(int v) { a = v; }
};

class B: public A {
protected:
  int b;
public:
  B() { b = 0; }
};

然后假设我想要一种像这样自动扩展类的方法:

class Base {
public:
   virtual ~Base() {}
};

template <typename T>
class Test: public T, public Base {};

我可以做出的一个真正重要保证是BaseTest 都不会有任何其他成员变量或方法。它们本质上是空类。

(潜在的)危险代码如下:

int main() {
  B *b = new B();

  // dangerous part?
  // forcing Test<B> to point to to an address of type B
  Test<B> *test = static_cast<Test<B> *>(b);

  //
  A *a = dynamic_cast<A *>(test);
  a->setA(10);
  std::cout << "result: " << a->getA() << std::endl;
}

这样做的基本原理是我正在使用类似于 Test 的类,但为了使其当前工作,必须创建一个新实例 T(即 Test),并复制传递的实例。如果我可以将 Test 指向 T 的内存地址,那就太好了。

如果 Base 没有添加虚拟析构函数,并且由于 Test 没有添加任何内容,我认为这段代码实际上是可以的。但是,添加虚拟析构函数让我担心类型信息可能会添加到类中。如果是这种情况,那么它可能会导致内存访问冲突。

最后,我可以说这段代码在我的计算机/编译器 (clang) 上运行良好,尽管这当然不能保证它不会对内存造成不良影响和/或不会在另一个编译器/机器上完全失败。

【问题讨论】:

  • 如果您需要传递不透明的指针,为什么不直接使用void*。它将使含义清晰,并避免有人试图对Test&lt;B&gt;* 执行操作的风险。
  • 不使用void *的原因是我在后面的代码中使用了dynamic_cast(和main一样)。此外,Test&lt;T&gt; 只能在内部使用,不能从外部访问。当用户访问它时,它要么是T *,要么是指向T 的父级的指针。

标签: c++ inheritance polymorphism dynamic-cast static-cast


【解决方案1】:

删除指针时将调用虚拟析构函数Base::~Base。由于B 没有正确的 vtable(此处发布的代码中根本没有),因此不会很好地结束。

它只在这种情况下有效,因为你有内存泄漏,你永远不会删除test

【讨论】:

    【解决方案2】:

    您的代码会产生未定义的行为,因为它违反了严格的别名。即使没有,您也正在调用 UB,因为 B 和 A 都不是多态类,并且指向的对象不是多态类,因此 dynamic_cast 不能成功。当使用dynamic_cast 时,您正在尝试访问不存在的 Base 对象以确定运行时类型。

    我可以做出的一个非常重要的保证是,无论是 Base Test 也不会有任何其他成员变量或方法。他们是 基本上是空的类。

    这根本不重要——完全无关紧要。标准必须强制 EBO 才能使这一点开始变得重要,而事实并非如此。

    【讨论】:

    • 我认为在非多态情况下,TestBase 不添加任何东西这一事实很重要。我认为 c++11 标准对于简单的情况有特定的对象布局(因此是计算变量偏移量的宏)。
    • 另外,dynamic_cast 对我来说确实成功了。
    • 不是,也不是。一旦涉及到虚拟功能,这些东西就会消失。此外,Base 没有添加任何内容,它添加了一个虚函数。 dynamic_cast 为您工作的事实无关紧要。是UB。此外,别名使其成为 UB。
    【解决方案3】:

    只要您不使用Test&lt;B&gt;* 执行任何操作,并避免任何魔法,如智能指针或自动内存管理,就可以了。

    您应该确保寻找会检查对象的隐藏代码,例如调试打印或日志记录。我让调试器崩溃,因为我试图查看像这样设置的指针的值。我敢打赌这会给你带来一些痛苦,但你应该能够让它发挥作用。

    我认为真正的问题是维护。一些开发者需要多长时间才能对Test&lt;B&gt;* 进行操作?

    【讨论】:

    • 用于库,Test 将完全隐藏。只有当有人在我的代码中包装他们的类 T 时才会使用它,所以用户甚至不会看到使用的类。我打算打开内存保护看看会发生什么,但问题是即使它有效在我的机器上,我担心其他人的编译器会做一些非常不同的事情。
    猜你喜欢
    • 2021-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-26
    • 2011-12-28
    • 2011-03-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多