【问题标题】:Why does this static C++ cast work?为什么这个静态 C++ 强制转换有效?
【发布时间】:2017-10-19 12:49:34
【问题描述】:

想象一下这段代码:

class Base {
 public:
  virtual void foo(){}
};

class Derived: public Base {
  public:
    int i;
    void foo() override {}
    void do_derived() {
      std::cout << i;
    }
};

int main(){
  Base *ptr = new Base;
  Derived * static_ptr = static_cast<Derived*>(ptr);
  static_ptr->i = 10;  // Why does this work?
  static_ptr->foo(); // Why does this work?
  return 0;
}

为什么我在控制台上得到结果 10?我想知道,因为我认为 ptr 是指向基础对象的指针。因此该对象不包含 int i 或方法do_derived()。是否会自动生成新的派生对象?

当我也在 Base 类中声明了一个虚拟的do_derived() 方法时,选择了这个,但是为什么呢?

【问题讨论】:

  • 它不起作用。这是未定义的行为。
  • 未定义的行为有时可能似乎起作用,但这只是一种诱使您陷入虚假安全感的策略。它正在等待时机,并将在最糟糕的时刻开始持续且可重复地失败。
  • @πάνταῥεῖ 我认为他实际上是 不幸 :) 这种无声错误(或 UB)可能很难调试。
  • @Useless 你的意思是“不一致且不可重现在最糟糕的时刻失败”
  • @mcAngular2 区分编译的代码和正确的代码非常重要。编译的代码不一定正确。 c++ 标准指定了许多编译器不需要检测的限制,例如您对static_cast 的使用。见this link

标签: c++ casting


【解决方案1】:
int* i = new int{1};
delete i;
std::cout << *i << std::endl;

如果工作的定义是代码将编译和执行,这也将“工作”。

但是,这显然是未定义的行为,并且无法保证会发生什么。


在您的情况下,代码编译为static_cast 不会执行任何检查,它只是转换指针。访问尚未分配和初始化的内存仍然是未定义的行为。

【讨论】:

    【解决方案2】:

    正如 cmets 中提到的,“碰巧做到了你所期望的”与“工作”不同。

    让我们做一些修改:

    #include <iostream>
    #include <string>
    
    class Base{
    public:
        virtual  void foo(){
            std::cout << "Base::foo" << std::endl;
        }
    };
    
    class Derived: public Base{
    public:
        int a_chunk_of_other_stuff[1000000] = { 0 };
        std::string s = "a very long string so that we can be sure we have defeated SSO and allocated some memory";
        void foo() override {
            std::cout << "Derived::foo" << std::endl;
        }
        void do_derived() {
            std::cout << s << std::endl;
        }
    };
    
    int main(){
        Base *ptr = new Base;
        Derived * static_ptr = static_cast<Derived*>(ptr);
        static_ptr -> foo(); // does it though?
        static_ptr -> do_derived(); // doesn't work?
        static_ptr->a_chunk_of_other_stuff[500000] = 10;  // BOOM!
        return 0;
    }
    

    样本输出:

    Base::foo
    
    Process finished with exit code 11
    

    在这种情况下,没有任何操作符合我们的预期。对数组的赋值导致了段错误。

    【讨论】:

      【解决方案3】:

      为什么这个静态转换有效?

      因为静态转换是编译时检查器。 Base 和 Derived 之间存在关系。由于它有关系,静态演员相信是这种关系,也相信是程序员。所以作为一个程序员,你应该确保 Base 对象不应该被静态转换为派生类对象。

      【讨论】:

      • you should make sure that Base object should not be static casted to derived class object. 反对!如果这种类型转换的来源总是真正的派生类型(或其子类),那么这样的static_cast 是完全有效的。在演员表总是有​​效的情况下,我不相信总是dynamic_casting;那是为你不使用的东西买单。
      【解决方案4】:

      声明:

      Base *ptr = new Base;
      

      并不总是分配sizeof(Base) - 它可能会分配更多的内存。即使它确实分配了确切的sizeof(Base) 字节,也不一定意味着此范围之后的任何字节访问(即sizeof(Base)+n,n>1)都是无效的。

      因此我们假设 Base 类的大小为 4 字节(由于大多数编译器实现中的虚函数表,在 32 位平台上)。但是,new 运算符、堆管理 API、操作系统的内存管理和/或硬件确实为此分配分配了 16 个字节(假设)。这使得额外的12 字节有效!它使以下语句有效:

      static_ptr->i = 10;
      

      从现在开始,它尝试在前 4 个字节(多态类的大小 Base)之后写入 4 个字节(通常为sizeof(int))。

      函数调用:

      static_ptr->foo();
      

      只需调用Derived::foo,因为指针的类型是Derived,并且没有任何问题。编译器必须调用Derived::fooDerived::foo 方法甚至不尝试访问派生类(甚至基类)的任何数据成员。

      你打过电话吗:

      static_ptr->do_derived();
      

      正在访问派生的i 成员。它仍然有效,因为:

      • 函数调用始终有效,直到方法尝试访问数据成员(即从this 指针访问某些内容)。
      • 由于内存分配(UD 行为)

      请注意,以下是完全有效的:

      class Abc
      {
      public:
      void foo() { cout << "Safe"; }
      };
      
      int main()
      {
         Abc* p = NULL;
         p->foo(); // Safe
      }
      

      调用它有效,因为它转换为:

          foo(NULL);
      

      foo 在哪里:

      void foo(Abc* p)
      {
          // doesn't read anything out of pointer!
      }
      

      【讨论】:

      • 我认为您的 p-&gt;foo() 示例不安全,即使它没有从指针中读取任何内容。正如这个答案stackoverflow.com/a/2474021/2689797 中提到的,p-&gt;foo() 等效于(*p).foo(),它取消了对空指针的引用。
      • 编译器会将它们都转换为foo(p),其中p 将是this 中的this 指针foo
      • @EldritchCheese,但是,我确实同意它实际上是 UD。但是,我仍然说如果p-&gt;f() 有效,那么在某些编译器上(*p).f() 将有效。
      猜你喜欢
      • 1970-01-01
      • 2014-10-14
      • 2013-06-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多