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

后端 未结 3 976
南笙
南笙 2021-01-17 04:12

I have a question wrt malloc/realloc memory that will contain an array of class/struct (i tried both struct and class the issue remains) members that include std vectors. I

相关标签:
3条回答
  • 2021-01-17 04:28
    /* dynamically allocate memory */
    Comp= (voxel*)malloc(10*sizeof(voxel));
    

    Comp is now a pointer to uninitialized memory.

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

    This attempts to call Comp[i].operator=(v0), but Comp[i] is not a valid, initialized object. In a simple test/debug case, we may get lucky but in practice we'll get garbage and the vector will either try to free/use an invalid pointer.

    That doesn't just mean you have to calloc() the memory instead, you can't make assumptions about what values an initialized object expects to find.

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

    Comp2 is now a pointer to a single voxel, and no "realloc" was done.

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

    This is just bizzare. It starts with Comp2 pointing to a single voxel. Then you for some reason take the address of the first element (&Comp2[0]) rather than just using the address of the first element (Comp2), and you reallocate it to the same size. You then copy-assign v0 into the uninitialized memory at the last-but-one position:

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

    Short: You can't use malloc or calloc or realloc with non-pod objects. You might get away with it occasionally, but you are basically pointing a loaded shotgun at your foot.

    It also seems that I cannot necessarily set an initial size of a vector in a class/struct

    You can easily set the default size of a vector in a class, C++11 required (-std=c++11 or greater for gnu/clang compilers, VS2013 or higher)

    #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 讨论(0)
  • 2021-01-17 04:34

    It's probably nothing to do with the realloc. Your code already has undefined behaviour when you do this near the start:

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

    Comp[0] has never been initialized (since malloc returns uninitialized memory -- it cannot know what type you intend to use it for and so could not possibly initialize it even if it wanted to). Then your code attempts to assign to it. This is not permitted for complex types like vector.

    Why is it not permitted? In the case of vector, because when you assign to a vector that already holds data, it needs to free the old data. If there's nothing to free then it would free nothing. But uninitialized memory might have any values at all, so it may well appear to vector that there is something which should be freed, that in fact is not a freeable pointer at all, let alone something that vector should be freeing as a consequence of that assignment. Without initialization, some class invariant along the lines of "this pointer data member is always either a null pointer or else is the address of some memory that is the vector's responsibility" is violated and so the vector code does not work.

    Supposing that your code somehow makes it past this point, you still can't realloc memory containing a vector. From the point of view of the standard, this is because vector<double> is not a POD type, and so byte-by-byte copies of it (including that done by realloc) result in undefined behavior.

    From the point of view of a particular implementation, we might ask ourselves what code the implementer might write, which would go wrong in the case that vectors are copied byte-for-byte. One hypothetical answer is that in some circumstances the vector could contain a pointer into its own body (as part of a so-called small vector optimization) [Edit: actually, I think a small vector optimization isn't possible within the standard for other reasons, but my general point is that because vectors are not POD, the implementer is free to use their creativity]. If the vector is relocated then this pointer no longer points into the vector's own body and so the class invariants are not satisfied, and the code no longer works. To give implementers freedom to write code like this, your freedom as a user of the class is limited, and you are not permitted to relocate a vector (or in general any non-POD type) by byte-wise copying.

    0 讨论(0)
  • 2021-01-17 04:35

    If you use malloc() with non-POD classes, you must call constructors (via placement new) and destructors manually.

    Using an object which was not constructed properly results in undefined behavior, which often means a crash when it comes to pointers.

    Obviously, freeing a memory for object without a proper destruction of it causes UB too.

    Your code must look like this:

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

    This requirement also means that you can't use realloc() with non-POD types, because it wont call destructors for the old array and contructors for the new one for you.

    Manual reallocation code might look like this:

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

    And please keep in mind that above code is not really exception-safe. If a constructor throws an exception and you catch it, then you want to be sure that you properly destruct objects which were constructed. Thanks @SteveJessop.

    Now when you understand why malloc()/free() usually should be avoided in C++, I hope you'll return to a lot more safe new/delete, which do all that construction and destruction for you.

    0 讨论(0)
提交回复
热议问题