C++ 单继承的内存布局和这段 C 代码一样吗?

Posted

技术标签:

【中文标题】C++ 单继承的内存布局和这段 C 代码一样吗?【英文标题】:Is the memory layout of C++ single inheritance the same as this C code? 【发布时间】:2017-12-28 17:31:04 【问题描述】:

我正在使用一个用 C 编写的库,它可以像这样进行继承:

struct Base

   int exampleData;
   int (function1)(struct Base* param1, int param2);
   void (function2)(struct Base* param1, float param2);
   //...
;

struct Derived

   struct Base super;
   //other data...
;

struct Derived* GetNewDerived(/*params*/)

    struct Derived* newDerived = malloc(sizeof struct Derived);
    newDerived->super.function1 = /*assign function*/;
    newDerived->super.function2 = /*assign function*/;
    //...
    return newDerived;


int main()

    struct Derived *newDerieved = GetNewDerived(/*params*/);
    FunctionExpectingBase((struct Base*) newDerived);
    free(newDerived);

据我了解,这是因为指向 Derived 的指针与指向 Derived 的第一个成员的指针相同,因此转换指针类型足以将对象视为其“基类”。我可以编写分配给function1function2 的任何内容,以将传入指针从Base* 转换为Derived* 以访问新数据。

我正在扩展这样的代码的功能,但我使用的是 C++ 编译器。我想知道下面是否等同于上面。

class MyDerived : public Base

    int mydata1;
    //...

public:
    MyDerived(/*params*/)
    
        function1 = /*assign function pointer*/;
        function2 = /*assign function pointer*/;
        //...
    

    //...
;

int main()

    MyDerived *newDerived = new MyDerived(/*params*/);
    FunctionExpectingBase( static_cast<Base*>(newDerived) );
    delete newDerived;

我可以期望编译器以相同的方式在MyDerived 中布置内存,以便我可以执行相同的指针转换以将我的对象传递到他们的代码中吗?还是我必须继续写这个更像他们的 C 架构,而不是利用 C++ 编译器为我做一些更乏味的部分?

在这个问题的范围内,我只考虑单继承。

【问题讨论】:

这里的C是哪一部分? @coderredoc 库代码。 :) 啊,是的..我明白了......很好@SouravGhosh 我会将 C 库包装到 C++ 类或结构中,并在该包装类内部管理“C”指针(让您的生活更轻松)。确保使用 __cplusplus 预处理器定义将 C 标头包装在 extern "C" 构造中。 我认为你可以这样使用MyDerived,但编译时断言可以验证内存布局。因此,如果有人错误地将虚函数添加到 MyDerived 编译将失败。 【参考方案1】:

根据Adding a default constructor to a base class changes sizeof() a derived type 和When extending a padded struct, why can't extra fields be placed in the tail padding?,即使您只是将构造函数添加到MyDerived 或以任何其他方式使其成为非POD,内存布局也会发生变化。所以恐怕没有这样的保证。实际上,您可以使用适当的编译时断言来验证两种结构的相同内存布局,但这种解决方案似乎并不安全,并且不受标准 C++ 的支持。

另一方面,为什么你的 C++ 包装器 MyDerived 不能从 Derived 继承?这将是安全的(因为当Derived 被转换为Base 并返回时它可能是安全的,但我认为这是你无法控制的)。它可能会将MyDerived::MyDerived() 中的初始化代码更改为更详细,但我想这是正确解决方案的小代价。

【讨论】:

【参考方案2】:

对于您的问题,这并不重要,因为“客户端”代码只关心有一个有效的 Base* 指针:他们不会在 Derived 或其他任何内容中向下转换它,或者复制它。

【讨论】:

OP 在描述中说库确实转换为 Derived 好吧,那将是未定义的行为,因为它实际上不是 Derived 的实例。 不,允许将“标准布局”对象转换为其第一个成员:en.cppreference.com/w/cpp/language/data_members#Standard_layout(第三个要点)并且@Cody 的结构似乎是标准布局 我明白了,谢谢。所以看起来从Derived 继承是一个合适的解决方案。

以上是关于C++ 单继承的内存布局和这段 C 代码一样吗?的主要内容,如果未能解决你的问题,请参考以下文章

C++ 无虚函数的单继承内存模型

一文详解C++类的内存布局和虚函数底层实现机制

C++ 虚拟继承内存布局

c++虚表学习2

c++虚表学习2

c++虚表学习2