在 C++ 中,为什么需要“new”来动态创建对象而不仅仅是分配?

发布于 2024-10-16 03:13:21 字数 1659 浏览 2 评论 0 原文

我有这个简单的类层次结构:

class Base {
public:
    virtual int x( ) const = 0;
};

class Derived : public Base {
    int _x;
public:
    Derived( int x ) : _x(x) { }
    int x( ) const { return _x; }
};

如果我使用 malloc 分配 Derived 的实例,然后尝试访问多态函数 x ,程序崩溃(我遇到分段错误):

int main( ) {
    Derived *d;
    d = (Derived*) malloc( sizeof(Derived) );
    *d = Derived( 123 );

    std::cout << d->x() << std::endl; // crash

    return 0;
}

当然,我的实际应用程序要复杂得多(它是一种内存池)。


我很确定这是因为我分配d的方式:我没有使用new

我知道展示位置 运算符,这一定是我需要的,但我从未使用过它,并且有一些问题:

I've got this trivial class hierarchy:

class Base {
public:
    virtual int x( ) const = 0;
};

class Derived : public Base {
    int _x;
public:
    Derived( int x ) : _x(x) { }
    int x( ) const { return _x; }
};

If I use malloc to allocate an instance of Derived, and then try to access the polymorphic function x, program crashes (I get a segmentation fault):

int main( ) {
    Derived *d;
    d = (Derived*) malloc( sizeof(Derived) );
    *d = Derived( 123 );

    std::cout << d->x() << std::endl; // crash

    return 0;
}

Of course my actual application is a lot more complex (it's a sort of memory pool).


I'm pretty sure it's because of the way I allocate d: I didn't use new.

I know of placement new operator, which must be what I need, but I've never used it and have got some questions:

  • why is my application crashing, if I don't use new?

    What does new actually do?

    Why can't I just use the assignment operator to assign the value of Derived( 123 ); to the memory area pointed by d?

  • Would I need to use new also for non-polymorphic types?

    How about POD ones?

  • On the C++Faq I linked above it says that the memory region passed to placement new must be aligned for the object I'm creating.

    I know what alignment is, but I don't know how to check the alignment needed for my class.

    malloc manual says:

    The malloc() and calloc() functions return a pointer to the allocated memory that is suitably aligned for any kind of variable.

    And I hope that the alignment needed for my class is the class size as returned by sizeof, so that any address in the form address_returned_by_malloc + i * sizeof(my_class) is suitable to allocate my objects.

    Are my hopes right?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

失去的东西太少 2024-10-23 03:13:21

让我们继续往下看,

  1. 如果我不使用 new,为什么我的应用程序会崩溃?

虚拟表已损坏。

虚拟表卡在分配的内存之后。当您new一个类时,生成的代码将正确设置vtable。但是,malloc 不会正确初始化 vtable

要查看虚拟表,请运行
g++ -fdump-class-hierarchy

Vtable for Derived
Derived::_ZTV7Derived: 3u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI7Derived)
16    Derived::x

Class Derived
   size=16 align=8
   base size=12 base align=8
Derived (0x10209fc40) 0
    vptr=((& Derived::_ZTV7Derived) + 16u) <-- notice how this is part of the structure
  Base (0x10209fcb0) 0 nearly-empty
      primary-for Derived (0x10209fc40)

出于类似的原因,在不重载operator=的情况下,生成的汇编代码将只复制数据而不复制vtable[再次强调,编译器只知道复制数据,而不知道复制vtable]

如果你想查看带有有效 vtable 函数的基于指针的版本:

Derived e(123);
d = &e;
  1. 对于非多态类型,我还需要使用 new 吗?

如果您使用的是虚函数,那么是的,即使对于非多态类型,

  1. 我希望我的类所需的对齐是 sizeof 返回的类大小,以便任何形式为 address_returned_by_malloc + i * sizeof(my_class) 的地址都是适合分配我的对象。

对齐不是问题。

Let's go down the line

  1. why is my application crashing, if I don't use new?

Virtual table is corrupted.

The virtual table is stuck right after the allocated memory. when you new a class, the generated code will properly set up the vtable. However, malloc will not properly initialize the vtable

To see the virtual table, run
g++ -fdump-class-hierarchy

Vtable for Derived
Derived::_ZTV7Derived: 3u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI7Derived)
16    Derived::x

Class Derived
   size=16 align=8
   base size=12 base align=8
Derived (0x10209fc40) 0
    vptr=((& Derived::_ZTV7Derived) + 16u) <-- notice how this is part of the structure
  Base (0x10209fcb0) 0 nearly-empty
      primary-for Derived (0x10209fc40)

For a similar reason, without overloading operator=, the generated assembly code will only copy the data and not the vtable [again, the compiler only knows to copy the data, not the vtable]

If you want to see a pointer-based version with a valid vtable function:

Derived e(123);
d = &e;
  1. Would I need to use new also for non-polymorphic types?

If you are using virtual functions, then yes, even for non-polymorphic types

  1. I hope that the alignment needed for my class is the class size as returned by sizeof, so that any address in the form address_returned_by_malloc + i * sizeof(my_class) is suitable to allocate my objects.

Alignment is not an issue.

自由范儿 2024-10-23 03:13:21

因为 malloc 不会调用类的构造函数,并且不知道它可能具有的任何特定对齐要求。如果您需要使用malloc(不推荐),请查看placement new(假设您出于某种原因不想超载常规的new)。

Because malloc doesn't call the class's constructor, and doesn't know anything about any particular alignment requirements it might have. If you need to use malloc (not recommended), take a look at placement new (assuming you don't want to overload the regular new for some reason).

仙女 2024-10-23 03:13:21

我不相信使用 malloc 时会调用对象的构造函数。

I don't belive that the object's constructor is called when you use malloc.

萌无敌 2024-10-23 03:13:21

标准的 [basic.life] 部分说

对象的生命周期是对象的运行时属性。如果一个对象属于类或聚合类型,并且该对象或其成员之一是由普通默认构造函数以外的构造函数初始化的,则称该对象具有非平凡初始化。 [ 注意:通过简单的复制/移动构造函数进行的初始化是非简单的初始化。 — 尾注] T 类型对象的生命周期开始于:

  • 获得类型 T 具有正确对齐和大小的存储,并且
  • 如果对象进行了重要的初始化,则其初始化已完成。

由于您的类具有虚拟成员,因此需要进行重要的初始化。您无法分配生命周期尚未开始的对象,必须使用new初始化它。

section [basic.life] of the standard says

The lifetime of an object is a runtime property of the object. An object is said to have non-trivial initialization if it is of a class or aggregate type and it or one of its members is initialized by a constructor other than a trivial default constructor. [ Note: initialization by a trivial copy/move constructor is non-trivial initialization. — end note ] The lifetime of an object of type T begins when:

  • storage with the proper alignment and size for type T is obtained, and
  • if the object has non-trivial initialization, its initialization is complete.

Since your class has virtual members, it requires non-trivial initialization. You can't assign an object whose lifetime hasn't started, you have to initialize it with new.

耳钉梦 2024-10-23 03:13:21

具有虚拟成员的类包含一个指向所谓的 vtable 的指针 - 基本上是指向这些虚拟成员的实现的函数指针表。当您使用operator new时,会调用构造函数,即使它是隐式构造函数,也会正确设置指向虚函数表的指针。

但是,malloc 不会调用构造函数。 vtable指针未初始化,指向一些随机内存。当您尝试调用虚拟函数时,您会取消引用错误的指针并崩溃(未定义的行为)。

解决方案是在使用对象之前使用placement new 来初始化该对象:

int main( ) {
    Derived *d;
    d = (Derived*) malloc( sizeof(Derived) );
    new(d) Derived(123); // invoke constructor
// You could also do:
//    new(d) Derived;
//    *d = Derived( 123 );

    std::cout << d->x() << std::endl; // crash

    // Although in your case it does not matter, it's good to clean up after yourself by
    // calling the destructor
    d->~Derived();
    return 0;
}

需要注意的一些重要事项:

  • 对齐不是问题。 malloc 中的内存对于任何 C++ 类型都是正确对齐的。
  • 使用 = 进行分配没有帮助。 = 的默认实现会复制所有成员变量,但 vtable 指针不是成员,不会被复制。
  • POD 类型不需要构造。非 POD 类型可能需要也可能不需要(如果不需要,则这是未定义的行为)。特别地,构造函数还调用成员变量构造函数;因此,如果不构造外部对象,内部对象也可能会被破坏。

Classes with virtual members contain a pointer to a so-called vtable - basically a table of function pointers to the implementation of these virtual members. When you use operator new, the constructor is called, which, even if it is an implicit constructor, will set up this pointer to the vtable properly.

However, malloc does not call the constructor. The vtable pointer is left uninitialized, point to some random memory. When you then attempt to call a virtual function, you dereference a bad pointer and crash (undefined behavior).

The solution is to use placement new to initialize the object before using it:

int main( ) {
    Derived *d;
    d = (Derived*) malloc( sizeof(Derived) );
    new(d) Derived(123); // invoke constructor
// You could also do:
//    new(d) Derived;
//    *d = Derived( 123 );

    std::cout << d->x() << std::endl; // crash

    // Although in your case it does not matter, it's good to clean up after yourself by
    // calling the destructor
    d->~Derived();
    return 0;
}

Some important things to note:

  • Alignment is not a problem. Memory from malloc is properly aligned for any C++ type.
  • Assigning with = does not help. The default implementation of = copies all member variables, but the vtable pointer is not a member and is not copied.
  • Construction is not required for POD types. Non-POD types may or may not require it (it's undefined behavior if you don't). In particular, the constructor also calls member variable constructors; so if you don't construct the outer object, inner objects may be broken as well.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文