Allocating Memory
Get acquainted with the memory allocation methods in D.
We'll cover the following
Memory allocation methods
System languages allow programmers to specify the memory areas where objects should live. Such memory areas are commonly called buffers.
There are several methods of allocating memory. The simplest method is using a fixed-length array:
ubyte[100] buffer; // A memory area of 100 bytes
buffer
is ready to be used as a 100-byte memory area. Instead of ubyte
, it is also possible to define such buffers as arrays of void
, without any association to any type. Since void
cannot be assigned any value, it cannot have the .init
value either. Such arrays must be initialized by the special syntax =void
:
void[100] buffer = void; // A memory area of 100 bytes
We will use only GC.calloc
from the core.memory
module to reserve memory. That module has many other features that are useful in various situations.
Additionally, the memory allocation functions of the C standard library are available in the core.stdc.stdlib
module.
GC.calloc
allocates a memory area of the specified size pre-filled with all 0 values, and returns the beginning address of the allocated area:
import core.memory;
// ...
void * buffer = GC.calloc(100);
// A memory area of 100 zero bytes
Normally, the returned void*
value is cast
to a pointer of the proper type:
int * intBuffer = cast(int*)buffer;
However, the intermediate step is usually skipped, and the return value is cast
directly:
int * intBuffer = cast(int*)GC.calloc(100);
Instead of arbitrary values like 100, the size of the memory area is usually calculated by multiplying the number of elements needed with the size of each element:
// Allocate room for 25 ints
int * intBuffer = cast(int*)GC.calloc(int.sizeof * 25);
There is an important difference for classes: The size of a class variable and the size of a class object are not the same. .sizeof
is the size of a class variable and is always the same value: 8 on 64-bit systems and 4 on 32-bit systems. The size of a class object must be obtained by __traits(classInstanceSize)
:
// Allocate room for 10 MyClass objects
MyClass * buffer =
cast(MyClass*)GC.calloc(
__traits(classInstanceSize, MyClass) * 10);
When there is not enough memory in the system for the requested size, then a core.exception.OutOfMemoryError
exception is thrown:
void * buffer = GC.calloc(10_000_000_000);
The output on a system that does not have that much free space is:
core.exception.OutOfMemoryError
The memory areas that are allocated from the GC can be returned back to it using GC.free
:
GC.free(buffer);
However, calling free()
does not necessarily execute the destructors of the variables that live on that memory block. The destructors may be executed explicitly by calling destroy()
for each variable. Note that various internal mechanisms are used to call finalizers on class and struct variables during GC collection or freeing. The best way to ensure that these are called is to use the new operator when allocating variables. In this case, GC.free
will call the destructors.
Sometimes, the program may determine that a previously allocated memory area is all used up and does not have room for more data. It is possible to extend a previously allocated memory area by GC.realloc
. realloc()
takes the previously allocated memory pointer and the newly requested size, and it returns a new area:
void * oldBuffer = GC.calloc(100);
// ...
void * newBuffer = GC.realloc(oldBuffer, 200);
realloc()
tries to be efficient by not actually allocating new memory unless it is really necessary:
-
If the memory area following the old area is not in use for any other purpose and is large enough to satisfy the new request,
realloc()
adds that part of the memory to the old area, extending the buffer in-place. -
If the memory area following the old area is already in use or is not large enough, then
realloc()
allocates a new larger memory area and copies the contents of the old area to the new one. -
It is possible to pass
null
asoldBuffer
, in which caserealloc()
simply allocates new memory. -
It is possible to pass a size less than the previous one, in which case the remaining part of the old memory is returned back to the GC.
-
It is possible to pass 0 as the new size, in which case
realloc()
simply frees the memory.
GC.realloc
is adapted from the C standard library function realloc()
. For having such a complicated behavior, realloc()
is considered to have a badly designed function interface. A potentially surprising aspect of GC.realloc
is that even if the original memory has been allocated with GC.calloc
, the extended part is never cleared. Therefore, when it is important that the memory is zero-initialized, a function like reallocCleared()
below would be useful. You will see the meaning of blockAttributes
later:
Get hands-on with 1400+ tech skills courses.