对包括stdvector的类/结构数组使用malloc/realloc

Using malloc/realloc for array of classes/structs including std vector

本文关键字:malloc realloc 数组 结构 包括 stdvector 的类      更新时间:2023-10-16

我有一个问题,wrt malloc/realloc内存将包含一个包含std向量的类/结构(我尝试了结构和类,问题仍然存在)成员数组。我知道我可以通过使用新的std数组容器类来规避这个问题。然而,我想更好地理解为什么当我使用realloc而不是malloc时,以下小代码会崩溃(因为我在将更大的代码项目从C转换到C++的过程中遇到了这个问题)。我似乎也不一定能在类/结构中设置向量的初始大小(有些编译器允许,有些则不允许)——那么类中的向量是什么——一个舒适的指针?

谢谢,Kai

#include <stdlib.h>
#include <limits.h>
#include <float.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <sys/types.h>
#include <vector>
/* mpic++ -O3 -ffast-math -pedantic vec-alloc.cpp -o vec-alloc */
using namespace std;
class float_vector{
public:
  double x;
  double y;
  double z;
  float_vector() : x(0), y(0), z(0) {};
};

class voxel{
public:
  float_vector   x;
  vector<double> y;
  voxel() : x() {};
};
int main(){
  int i;
  double d =1.111;
  voxel v0, *Comp, *Comp2;
  /* dynamically allocate memory */
  Comp= (voxel*)malloc(10*sizeof(voxel));
  for(i=0;i<10;++i) Comp[i] = v0;
  printf("malloc donen");
  /* dynamically re-allocate memory */
  Comp2= (voxel*)malloc(sizeof(voxel));  
  printf("realloc donen");
  for(i=0;i<10;++i){
    Comp2 =(voxel*)realloc(&Comp2[0], (i+1)*sizeof(voxel));
    Comp2[i] = v0;
  }  
  printf("realloc donen");
  for(i=0;i<10;++i) Comp[i].y.push_back(d);
  for(i=0;i<10;++i) printf("%lfn",Comp[i].y[0]);
  for(i=0;i<10;++i) Comp2[i].y.push_back(d); // this crashes
  for(i=0;i<10;++i) printf("%lfn",Comp2[i].y[0]);
  return 1;
} 

如果将malloc()与非POD类一起使用,则必须手动调用构造函数(通过放置new)和析构函数。

使用未正确构造的对象会导致未定义的行为,这通常意味着指针崩溃。

显然,在没有对对象进行适当销毁的情况下释放对象的内存也会导致UB。

您的代码必须如下所示:

MyClass *arr = (MyClass *) malloc(10 * sizeof (MyClass));
for (int i = 0; i < 10; i++)
    new (arr + i) MyClass; // This line calls constructors
// Do something with the array here
for (int i = 0; i < 10; i++)
    arr[i].~MyClass(); // This line calls destructors.
free(arr);

这个要求也意味着你不能将realloc()与非POD类型一起使用,因为它不会为你调用旧数组的析构函数和新数组的构造函数。

手动重新分配代码可能如下所示:

MyClass *new_ptr = (MyClass *) malloc(new_size * sizeof (MyClass));
for (int i = 0; i < new_size; i++)
    new (new_ptr + i) MyClass((MyClass &&) old_ptr[i]);
for (int i = new_size; i < old_size; i++)
    new (new_ptr + i) MyClass;
for (int i = 0; i < old_size; i++)
    old_ptr[i].~MyClass();
free(old_ptr);

请记住,上面的代码并不是真正的异常安全。如果一个构造函数抛出一个异常,并且您捕捉到了它,那么您需要确保正确地解构了所构造的对象谢谢@StevenJessop

现在,当您理解为什么在C++中通常应该避免malloc()/free()时,我希望您能回到更安全的new/delete,它为您完成所有的构建和破坏

这可能与realloc无关。当你在接近开始时这样做时,你的代码已经有了未定义的行为:

for(i=0;i<10;++i) Comp[i] = v0;

Comp[0]从未初始化过(因为malloc返回未初始化的内存——它不知道你打算将其用于什么类型,所以即使它想初始化,也不可能初始化它)。然后您的代码尝试分配给它。这对于像vector这样的复杂类型是不允许的。

为什么不允许?在向量的情况下,因为当您指定给已经包含数据的向量时,它需要释放旧数据。如果没有什么可以释放的,那么它就什么都不会释放。但是,未初始化的内存可能有任何值,所以在vector看来,很可能有一些东西应该被释放,事实上,它根本不是一个可释放的指针,更不用说vector应该因为该赋值而释放的东西了。在没有初始化的情况下,违反了一些类不变量,即"该指针数据成员始终是空指针,或者是向量负责的某个内存的地址",因此vector代码不起作用。

假设您的代码以某种方式通过了这一点,那么您仍然无法使用包含vectorrealloc内存。从标准的角度来看,这是因为vector<double>不是POD类型,因此它的逐字节拷贝(包括realloc所做的拷贝)会导致未定义的行为。

从一个特定实现的角度来看,我们可能会问自己,实现者可能会写什么代码,在矢量被逐字节复制的情况下,这会出错。一个假设的答案是,在某些情况下,vector可以包含一个指向其自身的指针(作为所谓的小向量优化的一部分)[编辑:实际上,我认为由于其他原因,小向量优化在标准中是不可能的,但我的总体观点是,因为向量不是POD,所以实现者可以自由使用他们的创造力]。如果向量被重新定位,则该指针不再指向向量自身,因此类不变量不满足,代码也不再工作。为了让实现者可以自由地编写这样的代码,作为类的用户,您的自由度是有限的,并且不允许通过逐字节复制来重新定位向量(或者通常任何非POD类型)。

/* dynamically allocate memory */
Comp= (voxel*)malloc(10*sizeof(voxel));

Comp现在是指向未初始化内存的指针。

for(i=0;i<10;++i) Comp[i] = v0;

这将尝试调用Comp[i].operator=(v0),但Comp[i]不是有效的初始化对象。在一个简单的测试/调试案例中,我们可能会很幸运,但在实践中,我们会得到垃圾,矢量会尝试释放/使用无效指针。

这不仅仅意味着你必须calloc()内存,你不能假设初始化的对象期望找到什么值。

/* dynamically re-allocate memory */
Comp2= (voxel*)malloc(sizeof(voxel));  
printf("realloc donen");

Comp2现在是指向单个体素的指针,并且没有执行"realloc"。

for(i=0;i<10;++i){
  Comp2 =(voxel*)realloc(&Comp2[0], (i+1)*sizeof(voxel));
  Comp2[i] = v0;
}  

这太奇怪了。它从Comp2指向单个体素开始。然后,出于某种原因,您采用第一个元素(&Comp2[0])的地址,而不是仅使用第一个元素的地址(Comp2),并将其重新分配为相同的大小。然后将assign v0复制到除一个位置之外的最后一个未初始化的内存中:

Comp2 = [...uninit...]
for (i  = 0
realloc(i + 1 == 1)
Comp2 = [...uninit...]
              ^-- v0
i++
realloc(i+1 == 2)
Comp2 = [.....v0.....][...uninit...]
                            ^--v0

短:不能将malloccallocrealloc与非pod对象一起使用。你可能偶尔会逃脱惩罚,但你基本上是在用一把上膛的猎枪指着你的脚。

我似乎也不一定能在类/结构中设置向量的初始大小

您可以很容易地设置类中向量的默认大小,C++11是必需的(对于gnu/clang编译器,VS2013或更高版本,-std=c++11或更大)

#include <iostream>
#include <vector>
struct A {
    std::vector<int> v = { 1, 2, 3 }; // default population
};
struct B {
    std::vector<int> v;
    B() : v(4) {}
};
int main() {
    A a;
    B b;
    std::cout << a.v.size() << ", " << b.v.size() << "n";
    std::cout << "n";
    for (int v : a.v) { std::cout << v << "n"; }
    std::cout << "n";
    for (int v : b.v) { std::cout << v << "n"; }
}

http://ideone.com/KA9fWB