2016-08-06 69 views
0

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

感谢, 凯

#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 done\n"); 

    /* dynamically re-allocate memory */ 
    Comp2= (voxel*)malloc(sizeof(voxel)); 
    printf("realloc done\n"); 
    for(i=0;i<10;++i){ 
    Comp2 =(voxel*)realloc(&Comp2[0], (i+1)*sizeof(voxel)); 
    Comp2[i] = v0; 
    } 

    printf("realloc done\n"); 

    for(i=0;i<10;++i) Comp[i].y.push_back(d); 
    for(i=0;i<10;++i) printf("%lf\n",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("%lf\n",Comp2[i].y[0]); 

    return 1; 
} 
+2

1.您为什么使用'使用命名空间std;' - 的是[不好的主意](http://stackoverflow.com/questions/1452721/why-is-using-namespace-std-in-c-considered-bad-practice)。 2为什么你使用'malloc'而不是'new' –

+0

为什么你使用'printf'而不是'cout'? –

+5

@EdHeal:你为什么要检查代码而不回答问题? ;-) –

回答

1

这可能是无关的realloc。您的代码已经是未定义行为,当你做到这一点的附近开始:

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

Comp[0]从未初始化(因为malloc返回未初始化的内存 - 它不能知道你想要什么类型的使用它的人,因而也即使它想要,也不可能初始化它)。然后你的代码尝试分配给它。这是不允许的复杂类型,如vector

为什么不允许?在矢量的情况下,因为当您分配给已经存储数据的矢量时,它需要释放旧数据。如果没有东西可以释放,那么它就不会释放任何东西。但是未初始化的内存可能有任何值,所以很可能看起来有些东西应该被释放,实际上它根本不是一个可释放的指针,更不用说那些由于这种情况而应该释放的东西了,更不用说了。分配。在没有初始化的情况下,违反了“这个指针数据成员总是或者是空指针或者是某个内存的地址是矢量的职责”的某些类不变量被违反,所以vector代码不起作用。

假设你的代码以某种方式使它过去了这一点,你仍然不能realloc内存包含一个vector。从标准的角度来看,这是因为vector<double>不是POD类型,因此它的逐字节副本(包括由realloc完成的副本)导致未定义的行为。

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

+0

非常感谢您的信息。我现在理解非POD和malloc的问题 - 我习惯于C和汇编,并试图增强一个大的C代码(> 15 000行,30个模块),并认为std向量会有所帮助 - 添加了一些C++特性和现在正在尝试更关键的,如矢量,坏主意?我当然可以很容易地删除所有的malloc和sub,但是我使用realloc,只要我读取长度未知的数据文件就不知道如何做到这一点,然后没有realloc(除非我首先扫描检查多少数据),建议?谢谢Kai – Kai

+0

@Kai:不是重新分配一个体素数组,而是使用'vector '。 'vector'知道如何在需要时正确地扩展它的数组。 –

2

如果您将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类型,因为它不会调用析构函数旧阵列和contructors为新的一个给你。

手动重新分配代码可能是这样的:

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); 

并请记住,上面的代码是不是真的异常安全。如果一个构造函数抛出一个异常并且捕获它,那么你想确保你正确地销毁了构造的对象。 谢谢@SteveJessop。

现在,当你明白为什么malloc()/free()通常应该在C++中是可以避免的,我希望你回到了更多安全new/delete,该做的一切,建设和毁灭你。

+1

你给出了正确的概念,但所需的代码甚至比这更糟糕,因为它应该在10个构造函数调用之一抛出异常的情况下正常工作(并且破坏,但是很多已经被构建到该点) 。最好避免。 –

0
/* 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 done\n"); 

器Comp2现在是一个指向单个体素,并没有 “的realloc” 已完成。

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

这只是bizzare。它从Comp2开始指向一个单一的体素。然后,出于某种原因,请取第一个元素(&Comp2[0])的地址,而不是仅使用第一个元素的地址(Comp2),然后将其重新分配给相同的大小。在最后,但是,一个位置,你再复制-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所需(-std=c++11或更大用于GNU /铛编译器,VS2013或更高)

#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

+0

“ - 现在它指向一个足够容纳2个体素的数组” - “i”从0开始,所以第一次在循环周围为一个体素分配空间并分配给它(在索引处0)。第二次为两个分配空间并分配给第二个(在索引1)。 –

+0

@SteveJessop好点,我对sizeof指定了太多的意义。 – kfsone

相关问题