【问题标题】:Why does push_back() cause crash within malloc()'ed data?为什么 push_back() 会导致 malloc() 数据崩溃?
【发布时间】:2012-06-01 21:11:43
【问题描述】:

为什么会崩溃?我确实发现 malloc() 不调用构造函数,所以我自己手动调用它们,但它仍然崩溃,我不明白为什么。

PS。我知道 std::vector 和 new[] 存在。不要告诉我使用vectors/new[] 作为答案。

struct MyStruct {
    vector<int> list;
};
void make_crash(){
    MyStruct *array = (MyStruct *)malloc(100*sizeof(MyStruct));
    MyStruct element; // initialize element here since malloc() doesnt do it.
    array[0] = element; // copy, everything should be alright?
    array[0].list.push_back(1337); // nope, BANG!
    // The above line makes these:
    // First-chance exception at 0x7c970441 in test.exe: 0xC0000005: Access violation reading location 0xbaadf005.
    // First-chance exception at 0x00401cd0 in test.exe: 0xC0000005: Access violation reading location 0xbaadf00d.
    // Unhandled exception at 0x00401cd0 in test.exe: 0xC0000005: Access violation reading location 0xbaadf00d.
}

【问题讨论】:

  • 为什么你想这样做?
  • @K-ballo,因为我很愚蠢...
  • 如果我说我也是个白痴怎么办,我会加分吗? :P
  • 虽然被误导了,但你的问题仍然很有趣......我只是想知道是什么让某人甚至尝试这样做。
  • @K-ballo,为什么?确实,确实……生命的奥秘。

标签: c++ visual-c++ malloc stdvector


【解决方案1】:

array[0] = element; 行上,您正在调用array[0]operator=。由于 array[0] 未初始化,这是未定义的行为。调用任何方法或运算符,包括对未调用其构造函数的对象的 operator=,都是未定义的行为。

要解决您的问题,您需要使用placement new 来调用array[0] 的构造函数,或者只使用new 而不是malloc。除非您有充分的理由使用 malloc,否则后者更可取(甚至更好:使用向量)。

【讨论】:

  • ...更好的是使用std::vector。 (我认为你的意思是前者。)
【解决方案2】:

这不是就地初始化元素的方式。正在对不存在的对象调用(隐式创建的)赋值运算符(调用向量的赋值运算符),这显然是个坏消息。

您必须改用placement new:

new (array) MyStruct;

对于数组:

new (array) MyStruct[100];

【讨论】:

  • 是否有可能“破解”它以使其在没有 new[] 的情况下工作?只是感兴趣...
  • @Rookie 请记住,新位置和常规新位置是完全不同的两件事。我认为没有任何黑客行为不是未定义的行为。
  • @Pubby 您正在为单个元素使用放置新。虽然只要只关注数组的初始元素就足够了,就像在 OP 的示例中一样,您可能需要提及放置新的“数组”版本以确保完整性:MyStruct* array = new(memory) MyStruct[100];
  • @Rookie:你为什么坚持要打破C++的规则?这些规则让您知道您的代码可以在 other 编译器上运行。如果你不喜欢这些规则,C 是一种完全合法的编程语言。你不能同时拥有它。您可以使用 C 或 C++ 编写代码。
  • @NicolBolas,什么规则? MyStruct* array = new(memory) MyStruct[100] 不合法吗?
【解决方案3】:

当您分配给MyStruct

array[0] = element;

首先尝试破坏结构的旧成员 - 但没有任何,因为它们从未被构造。轰隆隆!

获得一百个MyStructs 的最简单方法是使用另一个向量

vector<MyStruct>  v(100);

无需使用malloc

【讨论】:

  • 我认为你解释得最好:“首先尝试破坏结构的旧成员”。我不知道赋值运算符也这样做。
  • 嗯,不确定这是否真的是崩溃的原因......因为它不会在赋值运算符中崩溃,而是在 push_back() 行中崩溃。我说的对吗?
  • 赋值是形式上未定义的行为,在这种情况下可能会损坏堆。下一次分配内存的尝试会注意到...
【解决方案4】:
MyStruct *array = (MyStruct *)malloc(100*sizeof(MyStruct));

这就是你出错的地方。

array 不是指向一个或多个 MyStruct 对象的指针,无论您给它什么类型。 malloc 的返回值是 void*。 C++ 的规则不允许您从 void* 隐式转换为其他类型,这就是为什么您必须将 (MyStruct*) 放在那里的原因。仅需要显式转换就应该告诉您您正在做一些阴暗的事情。

C++ 的规则规定,如果您将 void* 显式转换为某些 Type*(某些特殊类型之外),这void* 您是进行演员表最初是Type*,它本身被转换为void*。这不是原因;这个void* 来自malloc,从来都不是MyStruct*。您在对编译器撒谎,因此会引发未定义的行为。因此崩溃了。

如果您想要定义行为,那么您需要实际使用 C++,而不是您正在发明的这种“我不敢相信它不是 C++”的语言。例如:

void *block = malloc(100 * sizeof(MyStruct));
MyStruct* array = new(block) MyStruct[100];

请注意这里完全没有强制转换操作。

当然,删除这个数组很痛苦:

for(int i = 99; i >= 0; --i)
  array[i].~MyStruct();

free(block);

请注意,您必须以与构建它们相反的顺序将它们向后销毁。


想知道这些东西在内部是​​如何工作的。 new[] 是否会产生一些额外的内存来告诉我的 CPU 一些 malloc 没有的东西?以及为什么/什么/如何在没有 new[] 的情况下模仿它?还是新的安置?我如何用 CPU 的原始二进制代码编写它?有可能吗? new[] 到底有什么魔力?

所有这些都取决于实现。就语言而言,究竟发生了什么是明确定义的。除其他外,放置 new 将调用对象的构造函数。数组放置 new 将调用数组中所有对象的构造函数,从第一个到最后一个。如果其中一个抛出,那么它将在任何先前构造的对象上调用析构函数,然后发出异常。

具体做什么是依赖于实现的,只不过是继承是如何实现的等等。显然编译器会为它发出一些代码,但同样,发出的确切内容取决于实现。

只要您实际上是在编写 C++,就不可能“为 CPU 编写原始二进制代码”。要实现placement new,您必须能够调用类的构造函数。而且...嗯,这就是placement new 适用于 的原因。您甚至不允许获得指向构造函数的成员指针(即使可以,成员指针的内容也取决于实现,并且它们并不总是指向某个汇编函数的裸指针)。因此,如果没有特定于平台的陪审团,甚至无法识别构造函数代码。

您可以通过查看生成的程序集以调用placement new 来了解如何为特定系统执行此操作。但每个编译器都会有所不同。

【讨论】:

  • 为什么需要逆序销毁...?
  • @Rookie:因为它们是按照从头到尾的顺序构造的,所以您应该始终按照构造它们的相反顺序销毁对象。您不必严格必须,但这是正确的处理方式。
猜你喜欢
  • 2021-06-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多