2

I'm having trouble allocating memory on the heap for an array of strings. Allocating with new works but malloc segfaults each time. The reason I want to use malloc in the first place is that I don't want to call the constructor unnecessarily.

This works fine

std::string* strings = new std::string[6];

This doesn't

std::string* strings = (std::string *)malloc(sizeof(std::string[6]));

One thing I've noticed is that the first variant (using new) allocates 248 bytes of memory while the second allocates only 240. This 8 byte difference is constant no matter the size of the array from what I've gathered and I cannot find what the source of the difference is.

Here's the whole code that segfaults.

#include <iostream>

void* operator new(size_t size)
{
    std::cout << size << std::endl;
    return malloc(size);
}

void* operator new [](size_t size)
{
    std::cout << size << std::endl;
    return malloc(size);
}

int main() {
    std::string* strings = new std::string[6];
    strings = (std::string *)malloc(sizeof(std::string[6]));

    strings[0] = std::string("test");

    return 0;
}

Another thing I've noticed is that the above code seems to work if I use memset after malloc to set all of the bytes I allocated with malloc to 0. I don't understand where the 8 byte difference comes from if this works and also why this variant works at all. Why would it work just because I set all of the bytes to 0?

2
  • 9
    Don't do manual memory allocation. What you want is a std::vector<std::string>. You can then reserve the space you need and no constructors will be called. Commented Aug 15, 2022 at 22:35
  • 3
    Using C++ classes with C allocation functions will not work if you don't know what you are doing. And strings[0] = std::string("test"); especially is broken, because you never constructed any valid std::string objects in the memory you allocated Commented Aug 15, 2022 at 22:37

2 Answers 2

6

malloc() only allocates raw memory, but it does not construct any objects inside of that memory.

new and new[] both allocate memory and construct objects.

If you really want to use malloc() to create an array of C++ objects (which you really SHOULD NOT do!), then you will have to call the object constructors yourself using placement-new, and also call the object destructors yourself before freeing the memory, eg:

std::string* strings = static_cast<std::string*>(
    malloc(sizeof(std::string) * 6)
);

for(int i = 0; i < 6; ++i) {
    new (&strings[i]) std::string;
}

...

for(int i = 0; i < 6; ++i) {
    strings[i].~std::string();
}

free(strings);

In C++11 and C++14, you should use std::aligned_storage to help calculate the necessary size of the array memory, eg:

using string_storage = std::aligned_storage<sizeof(std::string), alignof(std::string)>::type;

void *buffer = malloc(sizeof(string_storage) * 6);

std::string* strings = reinterpret_cast<std::string*>(buffer);

for(int i = 0; i < 6; ++i) {
    new (&strings[i]) std::string;
}

...

for(int i = 0; i < 6; ++i) {
    strings[i].~std::string();
}

free(buffer);

In C++17 and later, you should use std::aligned_alloc() instead of malloc() directly, eg:

std::string* strings = static_cast<std::string*>(
    std::aligned_alloc(alignof(std::string), sizeof(std::string) * 6)
);

for(int i = 0; i < 6; ++i) {
    new (&strings[i]) std::string;
}

...

for(int i = 0; i < 6; ++i) {
    strings[i].~std::string();
}

std::free(strings);
Sign up to request clarification or add additional context in comments.

4 Comments

But am I not calling the constructor here strings[0] = std::string("test"); ? Why isn't this enough?
std::string("test") constructs a separate temporary string object, which you are then assigning to the string object stored in strings[0] via the string::operator= assignment operator - except that the target object wasn't constructed by malloc(), thus you are invoking undefined behavior by assigning to an uninitialized object.
@Redex: In that line, you are calling the constructor on a temporary object and then assigning that object to strings[0]. Therefore, you are calling the function strings[0].operator=, because std::string overloads the = operator. Calling this function requires the underlying object's fields to be valid, i.e. constructed. I don't know whether the behavior is also undefined for classes that do not overload the = operator. My guess is that assigning to an uninitialized object is only guaranteed to work with POD types.
@AndreasWenzel all copyable/movable class types have an overloaded operator=, either implicitly generated by the compiler or explicitly written by the user. And technically malloc()'ing an array of POD types does not create valid objects either, at least prior to C++20 with the introduction of implicit lifetimes of trivial types, but most earlier compilers let you get away with it.
5

Allocating via new means the constructor is run. Please always use new and delete with C++ classes (and std::string is a C++ class), whenever you can.

When you do malloc() / free(), only memory allocation is done, constructor (destructor) is not run. This means, the object is not initialized. Technically you might still be able to use placement new (i.e., new(pointer) Type) to initialize it, but it's better and more conformant to use classic new.

If you wanted to allocate multiple objects, that's what containers are for. Please use them. Multiple top-grade engineers work on std::vector<>, std::array<>, std::set<>, std::map<> to work and be optimal - it's very hard to beat them in performance, stability or other metrics and, even if you do, the next coder at the same company needs to learn into your specific data structures. So it's suggested not to use custom and locally implemented allocations where a container could be used, unless for a very strong reason (or, of course, didactic purposes).

2 Comments

"Please always use new and delete with C++ classes..., whenever you can" - or better, never use new and delete directly at all. Modern practices advise that when you need to create objects dynamically, use smart pointer factories like std::make_unique() or std::make_shared(), or use dynamic-sized containers like std::vector. Let the standard library handle the memory management for you.
@RemyLebeau There are levels of removing the bad practices. What I wrote is first step (compared to malloc), what you wrote is the next step :).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.