使用赋值将 C++ 对象复制到堆会导致虚函数调用出现段错误

问题描述 投票:0回答:1

我有一个带有虚函数的 C++ 类。如果我使用

=
将它复制到我有 malloc 的内存中,然后调用虚函数,我会得到一个分段错误。

#include <iostream>

class Greeter {
public:
    virtual void print_hi() {
        std::cout << "hello" << std::endl;
    }
    void print_bye() {
        std::cout << "bye" << std::endl;
    }
};


int main() {
    Greeter* greeters = (Greeter*) malloc(sizeof(Greeter) * 16);

    Greeter g;
    greeters[0] = g;
    greeters[0].print_bye(); // prints "bye"
    greeters[0].print_hi(); // segmentation fault
}

我期望使用赋值运算符

=
g
复制到
greeters[0]
,并且我的虚函数调用将正常工作。相反,似乎与虚函数指针相关的东西已经被破坏了。不过,常规方法(例如
print_bye()
)仍然有效。

如果你好奇为什么:我正在通过存储一个连续的对象数组来优化我的一些数据局部性(缓存)代码,而不是指向独立堆分配的对象的指针数组,并且可能不要紧紧包装。另外,我在 CUDA 中使用了一些代码,这就是为什么我使用

malloc
(或
cudaMalloc
)而不是
std::vector
.

我可以通过以下方式解决这个问题:

  1. 使用堆栈分配的数组
Greeter greeters[16]; // instead of the malloc line
  1. 使用新的放置
Greeter* g = new(&greeters[0]) Greeter();
g->print_hi();
  1. 使用 memcpy
Greeter g;
memcpy(&greeters[0], &g, sizeof(Greeter));
greeters[0].print_hi();

为什么这些方法有效,而赋值运算符却无效?

c++ virtual-functions
1个回答
0
投票

根本的问题是

greeters
从来没有被正确构造过,因此 不能在以后分配 并且只是假设它会工作。 (特别是
v-table
将包含垃圾。)

这是因为

malloc
不会构造,而
new
会。 (如果幸运的话还有其他选择。)

在这种情况下这尤其令人困惑,因为即使类看起来是空的,具有虚函数的类也具有“隐藏”状态并且赋值不需要覆盖此状态,因为该类没有成员。

使用

new
是正确的做法,程序有效。

#include <iostream>

class Greeter {
public:
    virtual void print_hi() {
        std::cout << "hello" << std::endl;
    }
    void print_bye() {
        std::cout << "bye" << std::endl;
    }
};


int main() {
    Greeter* greeters = new Greeter[16];

    Greeter g;
    greeters[0] = g;
    greeters[0].print_bye(); // prints "bye"
    greeters[0].print_hi();

    delete[] greeters;
}

https://godbolt.org/z/ejr4z4f1W

为了进一步说明这一点,尽管使用

malloc
是一个糟糕的想法,但问题是缺乏结构,在这种情况下明确说明:

int main() {
    std::allocator<Greeter> alloc;

    Greeter* greeters = alloc.allocate(16);
    for(auto p = greeters; p != greeters + 16; ++p){
        alloc.construct(p);  // comment this and you will get a seg fault too
    }

    Greeter g;
    greeters[0] = g;
    greeters[0].print_bye(); // prints "bye"
    greeters[0].print_hi();

    for(auto p = greeters; p != greeters + 16; ++p){
        alloc.destroy(p);
    }

    alloc.deallocate(greeters, 16);
}

https://godbolt.org/z/qW4feqxsj

现在,烧掉那本书!

© www.soinside.com 2019 - 2024. All rights reserved.