memory - D Programming Language

Menu

- Learn
- Documentation
  - Language Reference
  - Library Reference
  - Command-line Reference
  - Feature Overview
  - Articles
- Downloads
- Packages
- Community
  - Blog
  - Orgs using D
  - Twitter
  - Calendar
  - Discord (community-run)
  - Forums
  - IRC
  - Wiki
  - Issues
  - Contributing
  - Contributors
  - Foundation
  - Security Team
  - Donate
  - Sponsors
- Resources
  - Tour
  - Books
  - Tutorials
  - Tools
  - Editors
  - IDEs
  - run.dlang.io
  - Visual D
  - Acknowledgments
  - D Style
  - Glossary
  - Sitemap

Search
Report a bug
If you spot a problem with this page, click here to create a GitHub issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using a local clone.

# memory

D is a language that does not require explicit memory management. However, it is important for a system programmer to know how to manage memory when needed for special cases.

Memory management is a very broad topic. This chapter will introduce only the garbage collector (GC), allocating memory from it, and constructing objects at specific memory locations. I encourage you to research various memory management methods as well as the `std.allocator` module, which was still at experimental stage at the time of writing this book.

As in some of the previous chapters, when I write variable below, I mean any type of variable including `struct` and `class` objects.

##### Memory

Memory is a more significant resource than other system resources because both the running program and its data are located in the memory. The memory belongs ultimately to the operating system, which makes it available to programs to satisfy their needs. The amount of memory that a program uses may increase or decrease according to the immediate needs of a program. When a program terminates, the memory areas that it has been using are automatically returned back to the operating system.

The memory can be imagined like a large sheet of paper where the values of variables are noted down. Each variable is kept at a specific location where its value is written to and read from as needed. Once the lifetime of a variable ends, its place is used for another variable.

The `&` (address-of) operator is useful when experimenting with memory. For example, the following program prints the addresses of two variables that are defined next to each other:

    import std.stdio;

    void main() {
        int i;
        int j;

        writeln("i: ", &i);
        writeln("j: ", &j);
    }

Note: The addresses would likely be different every time the program is executed. Additionally, the mere act of taking the address of a variable disables the optimization that would otherwise make the variable live on a CPU register.

As can be seen from the output, the locations of the variables are four bytes apart:

    i: 7FFF2B633E28
    j: 7FFF2B633E2C

The last digits of the two addresses indicate that `i` lives in a memory location that is right before the location of `j`: 8 plus 4 (size of `int`) makes 12 (C in hexadecimal notation).

##### The garbage collector

The dynamic variables that are used in D programs live on memory blocks that are owned by the garbage collector (GC). When the lifetime of a variable ends (i.e. it's no longer being used), that variable is subject to being finalized according to an algorithm that is executed by the GC. If nothing else needs the memory location containing the variable, the memory may be reclaimed to be used for other variables. This algorithm is called garbage collection and an execution of the algorithm is called a garbage collection cycle.

The algorithm that the GC executes can roughly be described as the following. All of the memory blocks that can be reached directly or indirectly by pointers (including references) that are in the program roots are scanned. Any memory block that can be reached is tagged as being still in use and all the others are tagged as not being used anymore. The finalizers of objects and structs that live on inaccessible blocks are executed and those memory blocks are reclaimed to be used for future variables. The roots are defined as all of the program stack for every thread, all global and thread-local variables, and any additional data added via `GC.addRoot` or `GC.addRange`.

Some GC algorithms can move objects around to keep them together in one place in memory. To preserve program correctness, all of the pointers (and references) that point to such objects are automatically modified to point to the new locations. D's current GC does not do this.

A GC is said to be "precise" if it knows exactly which memory contains pointers and which doesn't. A GC is conservative if it scans all memory as if it were pointers. D's GC is partially conservative, scanning only blocks that contain pointers, but it will scan all data in those blocks. For this reason, in some cases blocks are not ever collected, thereby "leaking" that memory. Large blocks are more likely to be targeted by "false pointers". In some cases it may be recommended to manually free large blocks you are no longer using to avoid this problem.

The order of executing the finalizers is unspecified. For example, a reference member of an object may be finalized before the object that contains that member. For that reason, no class member that refers to a dynamic variable should be accessed inside the destructor. Note that this is very different from the deterministic destruction order of languages like C++.

A garbage collection cycle can be started for various reasons like needing to find space for more data. Depending on the GC implementation, because allocating new objects during a garbage collection cycle can interfere with the collection process itself, all of the running threads may have to be halted during collection cycles. Sometimes this can be observed as a hesitation in the execution of the program.

In most cases the programmer does not need to interfere with the garbage collection process. However, it is possible to delay or dispatch garbage collection cycles as needed by the functions defined in the `core.memory` module.

###### Starting and delaying garbage collection cycles

It may be desired to delay the execution of garbage collection cycles during a part of the program where it is important for the program to be responsive. `GC.disable` disables garbage collection cycles and `GC.enable` enables them again:

        GC.disable();

    // ... a part of the program where responsiveness is important ...

        GC.enable();

However, `GC.disable` is not guaranteed to prevent a garbage collection cycle from executing: If the GC needs to obtain more memory from the OS, but it cannot, it still goes ahead and runs a garbage collection cycle as a last-ditch effort to gain some available memory.

Instead of relying on garbage collections happening automatically at unspecified times, a garbage collection cycle can be started explicitly using `GC.collect()`:

    import core.memory;

    // ...

        GC.collect();    // starts a garbage collection cycle

Normally, the GC does not return memory blocks back to the operating system; it holds on to those memory pages for future needs of the program. If desired, the GC can be asked to give unused memory back to the operating system using `GC.minimize()`:

        GC.minimize();

##### Allocating memory

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 would be 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 in this chapter. That module has many other features that are useful in various situations. Additionally, the memory allocation functions of the C standard library are avaliable 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, that 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:

    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 these are called is to use the `new` operator when allocating variables. In that 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 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` as `oldBuffer`, in which case `realloc()` 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. For that reason, when it is important that the memory is zero-initialized, a function like `reallocCleared()` below would be useful. We will see the meaning of `blockAttributes` later below:

    import core.memory;

    /* Works like GC.realloc but clears the extra bytes if memory
     * is extended. */
    void * reallocCleared(
        void * buffer,
        size_t oldLength,
        size_t newLength,
        GC.BlkAttr blockAttributes = GC.BlkAttr.NONE,
        const TypeInfo typeInfo = null) {
        /* Dispatch the actual work to GC.realloc. */
        buffer = GC.realloc(buffer, newLength,
                            blockAttributes, typeInfo);

        /* Clear the extra bytes if extended. */
        if (newLength > oldLength) {
            import core.stdc.string;

            auto extendedPart = buffer + oldLength;
            const extendedLength = newLength - oldLength;

            memset(extendedPart, 0, extendedLength);
        }

        return buffer;
    }

The function above uses `memset()` from the `core.stdc.string` module to clear the newly extended bytes. `memset()` assigns the specified value to the bytes of a memory area specified by a pointer and a length. In the example, it assigns `0` to `extendedLength` number of bytes at `extendedPart`.

We will use `reallocCleared()` in an example below.

The behavior of the similar function `GC.extend` is not complicated like `realloc()`; it applies only the first item above: If the memory area cannot be extended in-place, `extend()` does not do anything and returns 0.

###### Memory block attributes

The concepts and the steps of a GC algorithm can be configured to some degree for each memory block by `enum BlkAttr`. `BlkAttr` is an optional parameter of `GC.calloc` and other allocation functions. It consists of the following values:

- `NONE`: The value zero; specifies no attribute.
- `FINALIZE`: Specifies that the objects that live in the memory block should be finalized.

  Normally, the GC assumes that the lifetimes of objects that live on explicitly-allocated memory locations are under the control of the programmer; it does not finalize objects on such memory areas. `GC.BlkAttr.FINALIZE` is for requesting the GC to execute the destructors of objects:

          Class * buffer =
              cast(Class*)GC.calloc(
                  __traits(classInstanceSize, Class) * 10,
                  GC.BlkAttr.FINALIZE);

  Note that `FINALIZE` depends on implementation details properly set up on the block. It is highly recommended to let the GC take care of setting up these details using the `new` operator.

- `NO_SCAN`: Specifies that the memory area should not be scanned by the GC.

  The byte values in a memory area may accidentally look like pointers to unrelated objects in other parts of the memory. When that happens, the GC would assume that those objects are still in use even after their actual lifetimes have ended.

  A memory block that is known to not contain any object pointers should be marked as `GC.BlkAttr.NO_SCAN`:

          int * intBuffer =
              cast(int*)GC.calloc(100, GC.BlkAttr.NO_SCAN);

  The `int` variables placed in that memory block can have any value without concern of being mistaken for object pointers.

- `NO_MOVE`: Specifies that objects in the memory block should not be moved to other places.
- `APPENDABLE`: This is an internal flag used by the D runtime to aid in fast appending. You should not use this flag when allocating memory.
- `NO_INTERIOR`: Specifies that only pointers to the block's first address exist. This allows one to cut down on "false pointers" because a pointer to the middle of the block does not count when tracing where a pointer goes.

The values of `enum BlkAttr` are suitable to be used as bit flags that we saw in the Bit Operations chapter. The following is how two attributes can be merged by the `|` operator:

        const attributes =
            GC.BlkAttr.NO_SCAN | GC.BlkAttr.NO_INTERIOR;

Naturally, the GC would be aware only of memory blocks that are reserved by its own functions and scans only those memory blocks. For example, it would not know about a memory block allocated by `core.stdc.stdlib.calloc`.

`GC.addRange` is for introducing unrelated memory blocks to the GC. The complement function `GC.removeRange` should be called before freeing a memory block by other means e.g. by `core.stdc.stdlib.free`.

In some cases, there may be no reference in the program to a memory block even if that memory block has been reserved by the GC. For example, if the only reference to a memory block lives inside a C library, the GC would normally not know about that reference and assume that the memory block is not in use anymore.

`GC.addRoot` introduces a memory block to the GC as a root, to be scanned during collection cycles. All of the variables that can be reached directly or indirectly through that memory block would be marked as alive. The complement function `GC.removeRoot` should be called when a memory block is not in use anymore.

###### Example of extending a memory area

Let's design a simple `struct` template that works like an array. To keep the example short, let's provide only the functionality of adding and accessing elements. Similar to arrays, let's increase the capacity as needed. The following program uses `reallocCleared()`, which has been defined above:

    struct Array(T) {
        T * buffer;         // Memory area that holds the elements
        size_t capacity;    // The element capacity of the buffer
        size_t length;      // The number of actual elements

        /* Returns the specified element */
        T element(size_t index) {
            import std.string;
            enforce(index < length,
                    format("Invalid index %s", index));

            return *(buffer + index);
        }

        /* Appends the element to the end */
        void append(T element) {
            writefln("Appending element %s", length);

            if (length == capacity) {
                /* There is no room for the new element; must
                 * increase capacity. */
                size_t newCapacity = capacity + (capacity / 2) + 1;
                increaseCapacity(newCapacity);
            }

            /* Place the element at the end */
            *(buffer + length) = element;
            ++length;
        }

        void increaseCapacity(size_t newCapacity) {
            writefln("Increasing capacity from %s to %s",
                     capacity, newCapacity);

            size_t oldBufferSize = capacity * T.sizeof;
            size_t newBufferSize = newCapacity * T.sizeof;

            /* Also specify that this memory block should not be
             * scanned for pointers. */
            buffer = cast(T*)reallocCleared(
                buffer, oldBufferSize, newBufferSize,
                GC.BlkAttr.NO_SCAN);

            capacity = newCapacity;
        }
    }

The capacity of the array grows by about 50%. For example, after the capacity for 100 elements is consumed, the new capacity would become 151. (The extra 1 is for the case of 0 length, where adding 50% would not grow the array.)

The following program uses that template with the `double` type:

    import std.stdio;
    import core.memory;
    import std.exception;

    // ...

    void main() {
        auto array = Array!double();

        const count = 10;

        foreach (i; 0 .. count) {
            double elementValue = i * 1.1;
            array.append(elementValue);
        }

        writeln("The elements:");

        foreach (i; 0 .. count) {
            write(array.element(i), ' ');
        }

        writeln();
    }

The output:

    Adding element with index 0
    Increasing capacity from 0 to 1
    Adding element with index 1
    Increasing capacity from 1 to 2
    Adding element with index 2
    Increasing capacity from 2 to 4
    Adding element with index 3
    Adding element with index 4
    Increasing capacity from 4 to 7
    Adding element with index 5
    Adding element with index 6
    Adding element with index 7
    Increasing capacity from 7 to 11
    Adding element with index 8
    Adding element with index 9
    The elements:
    0 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9

##### Alignment

By default, every object is placed at memory locations that are multiples of an amount specific to the type of that object. That amount is called the alignment of that type. For example, the alignment of `int` is 4 because `int` variables are placed at memory locations that are multiples of 4 (4, 8, 12, etc.).

Alignment is needed for CPU performance or requirements, because accessing misaligned memory addresses can be slower or cause a bus error. In addition, certain types of variables only work properly at aligned addresses.

###### The `.alignof` property

The `.alignof` property of a type is its default alignment value. For classes, `.alignof` is the alignment of the class variable, not the class object. The alignment of a class object is obtained by `__traits(classInstanceAlignment)`.

The following program prints the alignments of various types:

    import std.stdio;
    import std.meta;
    import std.traits;

    struct EmptyStruct {
    }

    struct Struct {
        char c;
        double d;
    }

    class EmptyClass {
    }

    class Class {
        char c;
    }

    void main() {
        alias Types = AliasSeq!(char, short, int, long,
                                double, real,
                                string, int[int], int*,
                                EmptyStruct, Struct,
                                EmptyClass, Class);

        writeln(" Size  Alignment  Type\n",
                "=========================");

        foreach (Type; Types) {
            static if (is (Type == class)) {
                size_t size = __traits(classInstanceSize, Type);
                size_t alignment = __traits(classInstanceAlignment, Type);

            } else {
                size_t size = Type.sizeof;
                size_t alignment = Type.alignof;
            }

            writefln("%4s%8s      %s",
                     size, alignment, Type.stringof);
        }
    }

The output of the program may be different in different environments. The following is a sample output:

     Size  Alignment  Type
    =========================
       1       1      char
       2       2      short
       4       4      int
       8       8      long
       8       8      double
      16      16      real
      16       8      string
       8       8      int[int]
       8       8      int*
       1       1      EmptyStruct
      16       8      Struct
      16       8      EmptyClass
      17       8      Class

We will see later below how variables can be constructed (emplaced) at specific memory locations. For correctness and efficiency, objects must be constructed at addresses that match their alignments.

Let's consider two consecutive objects of `Class` type above, which are 17 bytes each. Although 0 is not a legal address for a variable on most platforms, to simplify the example let's assume that the first object is at address 0. The 17 bytes of this object would be at adresses from 0 to 16:

         0    1           16
      ┌────┬────┬─ ... ─┬────┬─ ...
      │<────first object────>│
      └────┴────┴─ ... ─┴────┴─ ...

Although the next available address is 17, that location cannot be used for a `Class` object because 17 is not a multiple of the alignment value 8 of that type. The nearest possible address for the second object is 24 because 24 is the next smallest multiple of 8. When the second object is placed at that address, there would be unused bytes between the two objects. Those bytes are called padding bytes:

         0    1           16   17           23   24   25           30
      ┌────┬────┬─ ... ─┬────┬────┬─ ... ─┬────┬────┬────┬─ ... ─┬────┬─ ...
      │<────first object────>│<────padding────>│<───second object────>│
      └────┴────┴─ ... ─┴────┴────┴─ ... ─┴────┴────┴────┴─ ... ─┴────┴─ ...

The following formula can determine the nearest address value that an object can be placed at:

        (candidateAddress + alignmentValue - 1)
        / alignmentValue
        * alignmentValue

For that formula to work, the fractional part of the result of the division must be truncated. Since truncation is automatic for integral types, all of the variables above are assumed to be integral types.

We will use the following function in the examples later below:

    T * nextAlignedAddress(T)(T * candidateAddr) {
        import std.traits;

        static if (is (T == class)) {
            const alignment = __traits(classInstanceAlignment, T);
        } else {
            const alignment = T.alignof;
        }

        const result = (cast(size_t)candidateAddr + alignment - 1)
                       / alignment * alignment;
        return cast(T*)result;
    }

That function template deduces the type of the object from its template parameter. Since that is not possible when the type is `void*`, the type must be provided as an explicit template argument for the `void*` overload. That overload can trivially forward the call to the function template above:

    void * nextAlignedAddress(T)(void * candidateAddr) {
        return nextAlignedAddress(cast(T*)candidateAddr);
    }

The function template above will be useful below when constructing class objects by `emplace()`.

Let's define one more function template to calculate the total size of an object including the padding bytes that must be placed between two objects of that type:

    size_t sizeWithPadding(T)() {
        static if (is (T == class)) {
            const candidateAddr = __traits(classInstanceSize, T);

        } else {
            const candidateAddr = T.sizeof;
        }

        return cast(size_t)nextAlignedAddress(cast(T*)candidateAddr);
    }

###### The `.offsetof` property

Alignment is observed for members of user-defined types as well. There may be padding bytes between members so that the members are aligned according to their respective types. For that reason, the size of the following `struct` is not 6 bytes as one might expect, but 12:

    struct A {
        byte b;     // 1 byte
        int i;      // 4 bytes
        ubyte u;    // 1 byte
    }

    static assert(A.sizeof == 12);    // More than 1 + 4 + 1

This is due to padding bytes before the `int` member so that it is aligned at an address that is a multiple of 4, as well as padding bytes at the end for the alignment of the entire `struct` object itself.

The `.offsetof` property gives the number of bytes a member variable is from the beginning of the object that it is a part of. The following function prints the layout of a type by determining the padding bytes by `.offsetof`:

    void printObjectLayout(T)()
            if (is (T == struct) || is (T == union)) {
        import std.stdio;
        import std.string;

        writefln("=== Memory layout of '%s'" ~
                 " (.sizeof: %s, .alignof: %s) ===",
                 T.stringof, T.sizeof, T.alignof);

        /* Prints a single line of layout information. */
        void printLine(size_t offset, string info) {
            writefln("%4s: %s", offset, info);
        }

        /* Prints padding information if padding is actually
         * observed. */
        void maybePrintPaddingInfo(size_t expectedOffset,
                                   size_t actualOffset) {
            if (expectedOffset < actualOffset) {
                /* There is some padding because the actual offset
                 * is beyond the expected one. */

                const paddingSize = actualOffset - expectedOffset;

                printLine(expectedOffset,
                          format("... %s-byte PADDING",
                                 paddingSize));
            }
        }

        /* This is the expected offset of the next member if there
         * were no padding bytes before that member. */
        size_t noPaddingOffset = 0;

        /* Note: __traits(allMembers) is a 'string' collection of
         * names of the members of a type. */
        foreach (memberName; __traits(allMembers, T)) {
            mixin (format("alias member = %s.%s;",
                          T.stringof, memberName));

            const offset = member.offsetof;
            maybePrintPaddingInfo(noPaddingOffset, offset);

            const typeName = typeof(member).stringof;
            printLine(offset,
                      format("%s %s", typeName, memberName));

            noPaddingOffset = offset + member.sizeof;
        }

        maybePrintPaddingInfo(noPaddingOffset, T.sizeof);
    }

The following program prints the layout of the 12-byte `struct A` that was defined above:

    struct A {
        byte b;
        int i;
        ubyte u;
    }

    void main() {
        printObjectLayout!A();
    }

The output of the program showns where the total of 6 padding bytes are located inside the object. The first column of the output is the offset from the beginning of the object:

    === Memory layout of 'A' (.sizeof: 12, .alignof: 4) ===
       0: byte b
       1: ... 3-byte PADDING
       4: int i
       8: ubyte u
       9: ... 3-byte PADDING

One technique of minimizing padding is ordering the members by their sizes from the largest to the smallest. For example, when the `int` member is moved to the beginning of the previous `struct` then the size of the object would be less:

    struct B {
        int i;    // Moved up inside the struct definition
        byte b;
        ubyte u;
    }

    void main() {
        printObjectLayout!B();
    }

This time, the size of the object is down to 8 due to just 2 bytes of padding at the end:

    === Memory layout of 'B' (.sizeof: 8, .alignof: 4) ===
       0: int i
       4: byte b
       5: ubyte u
       6: ... 2-byte PADDING

###### The `align` attribute

The `align` attribute is for specifying alignments of variables, user-defined types, and members of user-defined types. The value provided in parentheses specifies the alignment value. Every definition can be specified separately. For example, the following definition would align `S` objects at 2-byte boundaries and its `i` member at 1-byte boundaries (1-byte alignment always results in no padding at all):

    align (2)               // The alignment of 'S' objects
    struct S {
        byte b;
        align (1) int i;    // The alignment of member 'i'
        ubyte u;
    }

    void main() {
        printObjectLayout!S();
    }

When the `int` member is aligned at a 1-byte boundary, there is no padding before it and this time the size of the object ends up being exactly 6:

    === Memory layout of 'S' (.sizeof: 6, .alignof: 4) ===
       0: byte b
       1: int i
       5: ubyte u

Although `align` can reduce sizes of user-defined types, there can be significant performance penalties when default alignments of types are not observed (and on some CPUs, using misaligned data can actually crash the program).

`align` can specify the alignment of variables as well:

        align (32) double d;    // The alignment of a variable

However, objects that are allocated by `new` must always be aligned at multiples of the size of the `size_t` type because that is what the GC assumes. Doing otherwise is undefined behavior. For example, if `size_t` is 8 bytes long, than the alignments of variables allocated by `new` must be a multiple of 8.

##### Constructing variables at specific memory locations

The `new` expression achieves three tasks:

1. Allocates memory large enough for the object. The newly allocated memory area is considered to be raw, not associated with any type or any object.
2. Copies the `.init` value of that type on that memory area and executes the constructor of the object on that area. Only after this step the object becomes placed on that memory area.
3. Configures the memory block so it has all the necessary flags and infrastructure to properly destroy the object when freed.

We have already seen that the first of these tasks can explicitly be achieved by memory allocation functions like `GC.calloc`. Being a system language, D allows the programmer manage the second step as well.

Variables can be constructed at specific locations with `std.conv.emplace`.

###### Constructing a struct object at a specific location

`emplace()` takes the address of a memory location as its first parameter and constructs an object at that location. If provided, it uses the remaining parameters as the object's constructor arguments:

    import std.conv;
    // ...
        emplace(address, /* ... constructor arguments ... */);

It is not necessary to specify the type of the object explicitly when constructing a `struct` object because `emplace()` deduces the type of the object from the type of the pointer. For example, since the type of the following pointer is `Student*`, `emplace()` constructs a `Student` object at that address:

            Student * objectAddr = nextAlignedAddress(candidateAddr);
    // ...
            emplace(objectAddr, name, id);

The following program allocates a memory area large enough for three objects and constructs them one by one at aligned addresses inside that memory area:

    import std.stdio;
    import std.string;
    import core.memory;
    import std.conv;

    // ...

    struct Student {
        string name;
        int id;

        string toString() {
            return format("%s(%s)", name, id);
        }
    }

    void main() {
        /* Some information about this type. */
        writefln("Student.sizeof: %#x (%s) bytes",
                 Student.sizeof, Student.sizeof);
        writefln("Student.alignof: %#x (%s) bytes",
                 Student.alignof, Student.alignof);

        string[] names = [ "Amy", "Tim", "Joe" ];
        const totalSize = sizeWithPadding!Student() * names.length;

        /* Reserve room for all Student objects.
         *
         * Warning! The objects that are accessible through this
         * slice are not constructed yet; they should not be
         * accessed until after they are properly constructed. */
        Student[] students =
            (cast(Student*)GC.calloc(totalSize))[0 .. names.length];

        foreach (i, name; names) {
            Student * candidateAddr = students.ptr + i;
            Student * objectAddr =
                nextAlignedAddress(candidateAddr);
            writefln("address of object %s: %s", i, objectAddr);

            const id = 100 + i.to!int;
            emplace(objectAddr, name, id);
        }

        /* All of the objects are constructed and can be used. */
        writeln(students);
    }

The output of the program:

    Student.sizeof: 0x18 (24) bytes
    Student.alignof: 0x8 (8) bytes
    address of object 0: 7F1532861F00
    address of object 1: 7F1532861F18
    address of object 2: 7F1532861F30
    [Amy(100), Tim(101), Joe(102)]

###### Constructing a class object at a specific location

Class variables need not be of the exact type of class objects. For example, a class variable of type `Animal` can refer to a `Cat` object. For that reason, `emplace()` does not determine the type of the object from the type of the memory pointer. Instead, the actual type of the object must be explicitly specified as a template argument of `emplace()`. (Note: Additionally, a class pointer is a pointer to a class variable, not to a class object. For that reason, specifying the actual type allows the programmer to specify whether to emplace a class object or a class variable.)

The memory location for a class object must be specified as a `void[]` slice with the following syntax:

        Type variable =
            emplace!Type(voidSlice,
                             /* ... constructor arguments ... */);

`emplace()` constructs a class object at the location specified by the slice and returns a class variable for that object.

Let's use `emplace()` on objects of an `Animal` hierarchy. The objects of this hierarchy will be placed side-by-side on a piece of memory that is allocated by `GC.calloc`. To make the example more interesting, we will ensure that the subclasses have different sizes. This will be useful to demonstrate how the address of a subsequent object can be determined depending on the size of the previous one.

    interface Animal {
        string sing();
    }

    class Cat : Animal {
        string sing() {
            return "meow";
        }
    }

    class Parrot : Animal {
        string[] lyrics;

        this(string[] lyrics) {
            this.lyrics = lyrics;
        }

        string sing() {
            /* std.algorithm.joiner joins elements of a range with
             * the specified separator. */
            return lyrics.joiner(", ").to!string;
        }
    }

The buffer that holds the objects will be allocated with `GC.calloc`:

        const capacity = 10_000;
        void * buffer = GC.calloc(capacity);

Normally, it must be ensured that there is always available capacity for objects. We will ignore that check here to keep the example simple and assume that the objects in the example will fit in ten thousand bytes.

The buffer will be used for constructing a `Cat` and a `Parrot` object:

        Cat cat = emplace!Cat(catPlace);
    // ...
        Parrot parrot =
            emplace!Parrot(parrotPlace, [ "squawk", "arrgh" ]);

Note that the constructor argument of `Parrot` is specified after the address of the object.

The variables that `emplace()` returns will be stored in an `Animal` slice later to be used in a `foreach` loop:

        Animal[] animals;
    // ...
        animals ~= cat;
    // ...
        animals ~= parrot;

        foreach (animal; animals) {
            writeln(animal.sing());
        }

More explanations are inside the code comments:

    import std.stdio;
    import std.algorithm;
    import std.conv;
    import core.memory;

    // ...

    void main() {
        /* A slice of Animal variables (not Animal objects). */
        Animal[] animals;

        /* Allocating a buffer with an arbitrary capacity and
         * assuming that the two objects in this example will fit
         * in that area. Normally, this condition must be
         * validated. */
        const capacity = 10_000;
        void * buffer = GC.calloc(capacity);

        /* Let's first place a Cat object. */
        void * catCandidateAddr = buffer;
        void * catAddr = nextAlignedAddress!Cat(catCandidateAddr);
        writeln("Cat address   : ", catAddr);

        /* Since emplace() requires a void[] for a class object,
         * we must first produce a slice from the pointer. */
        size_t catSize = __traits(classInstanceSize, Cat);
        void[] catPlace = catAddr[0..catSize];

        /* Construct a Cat object inside that memory slice and
         * store the returned class variable for later use. */
        Cat cat = emplace!Cat(catPlace);
        animals ~= cat;

        /* Now construct a Parrot object at the next available
         * address that satisfies the alignment requirement. */
        void * parrotCandidateAddr = catAddr + catSize;
        void * parrotAddr =
            nextAlignedAddress!Parrot(parrotCandidateAddr);
        writeln("Parrot address: ", parrotAddr);

        size_t parrotSize = __traits(classInstanceSize, Parrot);
        void[] parrotPlace = parrotAddr[0..parrotSize];

        Parrot parrot =
            emplace!Parrot(parrotPlace, [ "squawk", "arrgh" ]);
        animals ~= parrot;

        /* Use the objects. */
        foreach (animal; animals) {
            writeln(animal.sing());
        }
    }

The output:

    Cat address   : 7F0E343A2000
    Parrot address: 7F0E343A2018
    meow
    squawk, arrgh

Instead of repeating the steps inside `main()` for each object, a function template like `newObject(T)` would be more useful.

##### Destroying objects explicitly

The reverse operations of the `new` operator are destroying an object and returning the object's memory back to the GC. Normally, these operations are executed automatically at unspecified times.

However, sometimes it is necessary to execute destructors at specific points in the program. For example, an object may be closing a `File` member in its destructor and the destructor may have to be executed immediately when the lifetime of the object ends.

`destroy()` calls the destructor of an object:

        destroy(variable);

After executing the destructor, `destroy()` sets the variable to its `.init` state. Note that the `.init` state of a class variable is `null`; so, a class variable cannot be used once destroyed. `destroy()` merely executes the destructor. It is still up to the GC when to reuse the piece of memory that used to be occupied by the destroyed object.

Warning: When used with a struct pointer, `destroy()` must receive the pointee, not the pointer. Otherwise, the pointer would be set to `null` but the object would not be destroyed:

    import std.stdio;

    struct S {
        int i;

        this(int i) {
            this.i = i;
            writefln("Constructing object with value %s", i);
        }

        ~this() {
            writefln("Destroying object with value %s", i);
        }
    }

    void main() {
        auto p = new S(42);

        writeln("Before destroy()");
        destroy(p);                        // ← WRONG USAGE
        writeln("After destroy()");

        writefln("p: %s", p);

        writeln("Leaving main");
    }

When `destroy()` receives a pointer, it is the pointer that gets destroyed (i.e. the pointer becomes `null`):

    Constructing object with value 42
    Before destroy()
    After destroy()  ← The object is not destroyed before this line
    p: null          ← Instead, the pointer becomes null
    Leaving main
    Destroying object with value 42

For that reason, when used with a struct pointer, `destroy()` must receive the pointee:

        destroy(*p);                       // ← Correct usage

This time the destructor is executed at the right spot and the pointer is not set to `null`:

    Constructing object with value 42
    Before destroy()
    Destroying object with value 42  ← Destroyed at the right spot
    After destroy()
    p: 7FB64FE3F200                  ← The pointer is not null
    Leaving main
    Destroying object with value 0   ← Once more for S.init

The last line is due to executing the destructor one more time for the same object, which now has the value `S.init`.

##### Constructing objects at run time by name

The `factory()` member function of `Object` takes the fully qualified name of a class type as parameter, constructs an object of that type, and returns a class variable for that object:

    module test_module;

    import std.stdio;

    interface Animal {
        string sing();
    }

    class Cat : Animal {
        string sing() {
            return "meow";
        }
    }

    class Dog : Animal {
        string sing() {
            return "woof";
        }
    }

    void main() {
        string[] toConstruct = [ "Cat", "Dog", "Cat" ];

        Animal[] animals;

        foreach (typeName; toConstruct) {
            /* The pseudo variable __MODULE__ is always the name
             * of the current module, which can be used as a
             * string literal at compile time. */
            const fullName = __MODULE__ ~ '.' ~ typeName;
            writefln("Constructing %s", fullName);
            animals ~= cast(Animal)Object.factory(fullName);
        }

        foreach (animal; animals) {
            writeln(animal.sing());
        }
    }

Although there is no explicit `new` expression in that program, three class objects are created and added to the `animals` slice:

    Constructing test_module.Cat
    Constructing test_module.Dog
    Constructing test_module.Cat
    meow
    woof
    meow

Note that `Object.factory()` takes the fully qualified name of the type of the object. Also, the return type of `factory()` is `Object`; so, it must be cast to the actual type of the object before being used in the program.

##### Summary

- The garbage collector scans the memory at unspecified times, determines the objects that cannot possibly be reached anymore by the program, destroys them, and reclaims their memory locations.
- The operations of the GC may be controlled by the programmer to some extent by `GC.collect`, `GC.disable`, `GC.enable`, `GC.minimize`, etc.
- `GC.calloc` and other functions reserve memory, `GC.realloc` extends a previously allocated memory area, and `GC.free` returns it back to the GC.
- It is possible to mark the allocated memory by attributes like `GC.BlkAttr.NO_SCAN`, `GC.BlkAttr.NO_INTERIOR`, etc.
- The `.alignof` property is the default memory alignment of a type. Alignment must be obtained by `__traits(classInstanceAlignment)` for class objects.
- The `.offsetof` property is the number of bytes a member is from the beginning of the object that it is a part of.
- The `align` attribute specifies the alignment of a variable, a user-defined type, or a member.
- `emplace()` takes a pointer when constructing a `struct` object, a `void[]` slice when constructing a `class` object.
- `destroy()` executes the destructor of objects. (One must destroy the struct pointee, not the struct pointer.)
- `Object.factory()` constructs objects with their fully qualified type names.

macros: TITLE=Memory Management DESCRIPTION=The memory, the garbage collector, and managing memory explicitly. KEYWORDS=d programming language tutorial book integer gc memory
Copyright © 1999-2026 by the D Language Foundation | Page generated by Ddoc on Wed Aug 19 13:28:54 2026