【问题标题】:Segfault with inheritence and casting带有继承和强制转换的段错误
【发布时间】:2021-11-28 12:42:26
【问题描述】:

我正在使用一个现有的C++,看起来像这样

class Geometry {
public:
    enum {BOX, MESH} type;
    std::string name;
};

class Mesh : public Geometry {
public:
    std::string filename;
};

std::shared_ptr<Geometry> getGeometry() {
    std::shared_ptr<Geometry> geom = std::make_shared<Geometry>();
    geom->name = "geometry";

    Mesh *mesh = new Mesh();
    mesh->name = "name";
    mesh->filename = "filename";
    mesh->type = Geometry::MESH;
    geom.reset(mesh);

    return geom;
}

我需要扩展这些类提供的功能,所以我派生了这两个类

#include <memory>
#include <iostream>

class Geometry {
public:
    enum {BOX, MESH} type;
    std::string name;
};

class Mesh : public Geometry {
public:
    std::string filename;
};

std::shared_ptr<Geometry> getGeometry() {
    std::shared_ptr<Geometry> geom = std::make_shared<Geometry>();
    geom->name = "geometry";

    Mesh *mesh = new Mesh();
    mesh->name = "name";
    mesh->filename = "filename";
    mesh->type = Geometry::MESH;
    geom.reset(mesh);

    return geom;
}

class GeometryDerived : public Geometry {
public:
    void consumeGeometry() {
        Geometry geom = static_cast<Geometry>(*this);
        std::cout << geom.name << std::endl;
        if(geom.type == Geometry::MESH) {
            Mesh *mesh = static_cast<Mesh*>(&geom);
            std::cout << mesh->filename << std::endl;
            // mesh->get_output();
        }
    }
};

class MeshDerived : public Mesh {
public:
    std::string get_output() {
        return this->filename;        
    }
};



int main(int argc, char** argv)
{
    std::shared_ptr<Geometry> geom = getGeometry(); // this object is returned by the library

    std::shared_ptr<GeometryDerived> geomDerived = std::static_pointer_cast<GeometryDerived>(geom);
    geomDerived->consumeGeometry();

    return 0;
}

我得到一个segfault 行(在我的实际代码中,它的垃圾字符串)

std::cout << mesh->filename << std::endl;

虽然其他ints 和doubles 打印正确。即使geom.name 也能正确打印。这里的字符串是怎么回事?在这里投射物体的正确方法是什么?两次投射物体看起来很难看。下面是GDB 的输出

GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./cpp-example...
(gdb) run
Starting program: /home/user/cpp-example
name

Program received signal SIGSEGV, Segmentation fault.
__memmove_avx_unaligned_erms () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:383
383 ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S: No such file or directory.
(gdb) bt
#0  __memmove_avx_unaligned_erms () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:383
#1  0x00007ffff7c247b2 in _IO_new_file_xsputn (n=140737488346512, data=0xdb8bbc853a3b3400, f=<optimized out>) at fileops.c:1236
#2  _IO_new_file_xsputn (f=0x7ffff7d7e6a0 <_IO_2_1_stdout_>, data=0xdb8bbc853a3b3400, n=140737488346512) at fileops.c:1197
#3  0x00007ffff7c18541 in __GI__IO_fwrite (buf=0xdb8bbc853a3b3400, size=1, count=140737488346512, fp=0x7ffff7d7e6a0 <_IO_2_1_stdout_>) at libioP.h:948
#4  0x00007ffff7ed2824 in std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) ()
   from /lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x0000555555556858 in GeometryDerived::consumeGeometry (this=0x55555556def0) at ../downcasting.cpp:23
#6  0x0000555555556513 in main (argc=1, argv=0x7fffffffdec8) at ../downcasting.cpp:53

编辑 1: 以下更改工作正常,但这不是我想要的

int main(int argc, char** argv)
{
    std::shared_ptr<Geometry> geom = getGeometry();

    if(geom->type == Geometry::MESH) {
        std::shared_ptr<MeshDerived> meshDerived = std::static_pointer_cast<MeshDerived>(geom);
        std::cout << meshDerived->filename << std::endl;
    }

    // std::shared_ptr<GeometryDerived> geomDerived = std::static_pointer_cast<GeometryDerived>(geom);
    // geomDerived->consumeGeometry();

    return 0;
}

【问题讨论】:

  • 您认为Geometry geom = static_cast&lt;Geometry&gt;(*this); 行做了什么? (如果您不尝试制作副本,为什么还要费心铸造呢?)

标签: c++ inheritance casting shared-ptr


【解决方案1】:

您的代码存在多个问题:

  • GeometryDerived::consumeGeometry中,Geometry geom = static_cast&lt;Geometry&gt;(*this);通过slicing当前对象创建一个Geometry类型的新对象。稍后在该函数中,您执行Mesh *mesh = static_cast&lt;Mesh*&gt;(&amp;geom);,它返回一个无效指针(因为geom 不是Mesh)。通过该指针访问数据是 UB。 (由于大多数编译器使用的内存布局,它可能适用于大多数实现中在 Geometry 中声明的数据成员;这并不能很好地定义行为。)

  • static_pointer_cast 不检查指向的对象是否实际上是请求的类型。即使对象是不同的类型,转换也会成功,但返回的指针将是无效的。 getGeometry 函数返回一个指向 Mesh 对象的指针,该对象与 GeometryDerived 无关,因此强制转换返回一个无效指针。


Edit 1 部分的评论。不,这行也是错误的:

std::shared_ptr<MeshDerived> meshDerived = std::static_pointer_cast<MeshDerived>(geom);

原因和我上面提到的一样:getGeometry 返回一个指向Mesh 的指针,而不是MeshDerived。该代码可能适用于大多数编译器,因为 MeshDerivedMesh 具有相同的内存布局,但这仍然不是有效的 C++。

您似乎想在运行时向现有类“添加方法”。这在 C++ 中是不可能的。您必须在主类中定义所需的方法(例如Mesh)或使用visitor pattern,即在一个单独的类中定义新功能,该类将通过其公共成员对Mesh等进行操作。

【讨论】:

  • 切片对象是什么意思?但这不是你会沮丧的方式吗?为什么你说geom 不是Meshgeom.reset(mesh);呢?
  • 对于static_pointer_cast,我已经剥离了大部分代码,但我已经实施了检查。
  • @harsh 见What is object slicing?geom 不是指针,它是 Geometry 类型的对象,基类。通过构造它,您将丢弃派生类中声明的所有数据。
  • 有道理!也许我将不得不采用我在编辑中提到的解决方案
  • @harsh 我已经更新了对您的编辑发表评论的答案。
猜你喜欢
  • 2012-04-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-18
  • 2011-01-05
  • 2010-11-30
  • 2017-06-16
相关资源
最近更新 更多