Uninitialized memory in C++

3.5k views Asked by At

operator new in C++ both allocates and initializes memory (by calling the default constructor). What if I want the memory to be uninitialized? How do I allocate memory in that case?

In C I could have used malloc for instance which would just allocate memory, not initialize it.

3

There are 3 answers

4
Jive Dadson On BEST ANSWER

It is possible, but a little tricky to separate allocation from construction. (Bjarne Stroustrup and I discussed this at length, ca. 1985.) What you have to do is to use ::operator new to obtain raw memory. Later on, you can use placement-new or whatever to do the initialization if the object-type requires it. That is how the the default allocator for the STL containers separates allocation and construction.

This obtains raw memory for an object of type U:

U *ptr = (U*) ::operator new (sizeof(U));
  // allocates memory by calling: operator new (sizeof(U))
  // but does not call U's constructor

Speaking of STL... You can specify your own allocator for the ::std:: containers. For example, if you allocate arrays of floats using std::vector<float>, it will gratuitously initialize them to zeros. (There is a specialization for vector<float>.) You can instead roll your own: std::vector<float, my_own_allocator>.

The custom allocator in the following link inherits functions from the default allocator to do almost everything - including the allocation of raw memory. It overrides the default behavior of construct() - so as to do nothing - when the actual constructor is trivial and cannot throw an exception.

--> Is it possible? std::vector<double> my_vec(sz); which is allocated but not initialized or filled

See how it uses placement new.

::new(static_cast<void*>(ptr)) U;
 // Calls class U constructor on ptr.

Your allocator could even be written such that when compiling for debug, it fills the array with illegal numbers (NaN's), and leaves the memory uninitialized when compiling for release. Some of the nastiest bugs I have ever seen came about when default zeros worked -- until they didn't. DISTRUST DEFAULTS.

One more capital letter thing... AVOID EARLY OPTIMIZATION. Are the computer cycles you save from not initializing objects twice actually worth the effort?

2
chris On

There are two main techniques people use to delay creation of an object. I will show how they apply for a single object, but you can extend these techniques to a static or dynamic array.

Aligned Storage

The first approach is using std::aligned_storage, which is a glorified char array with alignment taken into consideration:

template<typename T>
class Uninitialized1 {
    std::aligned_storage_t<sizeof(T)> _data;

public:
    template<typename... Args>
    void construct(Args... args) {
        new (&_data) T(args...);
        std::cout << "Data: " << *reinterpret_cast<T*>(&_data) << "\n";
    }
};

Note that I have left out things like perfect forwarding to keep the main point.

One drawback of this is that there's no way to have a constexpr constructor that takes a value to copy into the class. This makes it unsuitable for implementing std::optional.

Unions

A different approach uses plain old unions. With aligned storage, you have to be careful, but with unions, you have to be doubly careful. Don't assume my code is bug-free as is.

template<typename T>
class Uninitialized2 {
    union U {
        char dummy;
        T data;

        U() {}
        U(T t) : data(t) {
            std::cout << "Constructor data: " << data << "\n";
        }
    } u;

public:
    Uninitialized2() = default;
    Uninitialized2(T t) : u(t) {}

    template<typename... Args>
    void construct(Args... args) {
        new (&u.data) T(args...);
        std::cout << "Data: " << u.data << "\n";
    }
};

A union stores a strongly-typed object, but we put a dummy with trivial construction before it. This means that the default constructor of the union (and of the whole class) can be made trivial. However, we also have the option of initializing the second union member directly, even in a constexpr-compatible way.


One very important thing I left out is that you need to manually destroy these objects. You will need to manually invoke destructors, and that should bother you, but it's necessary because the compiler can't guarantee the object is constructed in the first place. Please do yourself a favour and study up on these techniques in order to learn how to utilize them properly, as there are some pretty subtle details, and things like ensuring every object is properly destroyed can become tricky.


I (barely) tested these code snippets with a small class and driver:

struct C {
    int _i;

public:
    explicit C(int i) : _i(i) {
        std::cout << "Constructing C with " << i << "\n";
    }

    operator int() const { return _i; }
};

int main() {
    Uninitialized1<C> u1;
    std::cout << "Made u1\n";
    u1.construct(5);

    std::cout << "\n";

    Uninitialized2<C> u2;
    std::cout << "Made u2\n";
    u2.construct(6);

    std::cout << "\n";

    Uninitialized2<C> u3(C(7));
    std::cout << "Made u3\n";
}

The output with Clang was as follows:

Made u1
Constructing C with 5
Data: 5

Made u2
Constructing C with 6
Data: 6

Constructing C with 7
Constructor data: 7
Made u3
0
2785528 On

new operator in C++ both allocates and initializes memory(by calling default constructor).

IMHO - you have misread what the new operator does.

  • The new operator allocates a block of memory big enough to hold the object.

  • 'new' does not initialize memory.

  • The ctor, when available, need not initialize memory, and you control that.


What if I do not want the memory to be un-initialized? [SIC] How do I allocate memory in that case?

  • The default ctor provided by the compiler does nothing. In class Foo, you can use the "Foo() = default;" to command the compiler to provide a default ctor.

  • You can order the compiler to disallow the default ctor. For class Foo, "Foo() = delete;" Unless you provide one, there will be no default ctor.

  • You can define your own default ctor that does nothing to memory. For your class, the ctor would probably have no initialization list, and a null body.


Note: There are many embedded systems where the requirement is for the ctor to either be disallowed or implemented to do nothing (that would cause memory equipment state changes). Research terms "warm-start" (software reset without affecting data flow in the quipment.) vs "cold-start" (software and equipment restarts) vs "power-bounce".

In some embedded systems, the memory-mapped i/o devices are not mapped into dynamic memory, and the OS does not administer it. It is common, in these cases, for the programmer to provide no-op ctor and no dtor for these objects.