Home > Software engineering >  C placement new, Invalid read and InvalidInvalid free() / delete / delete[] / realloc()
C placement new, Invalid read and InvalidInvalid free() / delete / delete[] / realloc()

Time:06-20

I'm experimenting the usage of placement new to try to understand how it works.

Executing the code below:

#include <iostream>

#define SHOW(x) std::cout << #x ": " << x << '\n'

template<typename T>
static T *my_realloc(T *ptr, size_t count) {
    return (T *) realloc((void *) ptr, count * sizeof(T));
}

template<typename T>
static void my_free(T *ptr) {
    free((T *) ptr);
}

int main() {

    constexpr int count = 40;
    
    int cap = 0;
    int size = 0;
    std::string *strs = nullptr;
    
    auto tmp_str = std::string();
    for(int i = 0; i < count; i  ) {
        tmp_str = std::to_string(i);
        
        if(size == cap) {
            if(cap == 0)
                cap = 1;
            else
                cap *= 2;
            strs = my_realloc(strs, cap);
        }
        new (&strs[size  ]) std::string(std::move(tmp_str));
    }

    for(int i = 0; i < size; i  )
        SHOW(strs[i]);
    
    std::destroy_n(strs, size);
    my_free(strs);

    return 0;
}

I get the errors:

    Invalid read of size 1
    Invalid free() / delete / delete[] / realloc()

Removing the line

    std::destroy_n(strs, size);

The error of invalid free is solved, but somehow all memory of the program is freed and no leaks are generated. But i can't find how the std::string destructor is called in the program.

CodePudding user response:

strs = my_realloc(strs, cap);

strs is a pointer to a std::string, and this will result in the contents of the pointer to be realloc()ed.

This is undefined behavior. C objects cannot be malloced, realloced, or freeed. Using a wrapper function, or placement new, at some point along the way, does not change that.

Everything from this point on is undefined behavior, and the resulting consequences are of no importance.

CodePudding user response:

If you want to store non-trivial types (such as std::string), then realloc simply cannot be used. You will find that standard library containers like e.g. std::vector will also not use it.

realloc may either extend the current allocation, without moving it in memory, or it might internally make a new allocation in separate memory and copy the contents of the old allocation to the new one. This step is performed as if by std::memcpy. The problem here is that std::memcpy will only work to actually create new objects implicitly in the new allocation and copy the values correctly if the type is trivially-copyable and if it and all of its subobjects are implicit-lifetime types. This definitively doesn't apply to std::string or any other type that manages some (memory) resource.

You are also forgetting to check the return value of realloc. If allocation failed, it may return a null pointer, which you will then use regardless, causing a null pointer dereference, as well as a memory leak of the old allocation.

Instead of using realloc you should make a new allocation for the new size, then placement-new copies of the objects already in the old allocation into the new one and then destroy the objects in the old allocation and free it.

If you want to guarantee that there won't be memory leaks when exceptions are thrown things become somewhat complicated. I suggest you look at the std::vector implementation of one of the standard library implementations if you want to figure out the details.

  • Related