DEV Community

Anna Voronina
Anna Voronina

Posted on

Silent foe or quiet ally: Brief guide to alignment in C++

Imagine your program is a model of clean code—reviewed, tested, and seemingly flawless. But its performance still falls short of expectations. You've checked everything you know. Maybe you've overlooked something? The issue might boil down to data alignment in memory. For many, this mechanism remains a mystery. Let's cover this complex topic together.

1339_Alignment/image1.png

Introduction

Have you ever wondered how data is actually stored in computer memory? Every variable in a program occupies a specific place in memory—a sequence of bytes at a particular address—and you can access each byte. However, the physical access to this data is more complex than it might seem.

Due to the performance difference between the processor and RAM, data first gets into fast cache. The CPU cache exchanges with RAM aligned blocks containing individual bytes that the program needs at that moment—not individual needed bytes.

Let's go through a specific scenario. A 4-byte int locates in memory in the following way—its start falls at the end of one 4-byte block, and its another part is at the start of the next one. To read this number, the processor performs two read operations instead of one, then extracts the needed parts from different blocks and unites them. The problem worsens if these blocks locate at different virtual memory pages. This not only slows down operations but also complicates data processing for the CPU. To avoid such complications, we use alignment.

So, what is alignment? It's a method of organizing data and accessing it in memory according to specified boundaries. A data type is "aligned" when its address is a multiple of a power of two, typically equal to the size of the type. Let's keep this key definition in mind for now.

How does this principle apply in code? Let's dive down the rabbit hole step by step, exploring through examples.

Natural alignment and padding

To understand this complex mechanism, let's start with the basic principles of placing primitive data types in memory. Look at this basic example:

#include <iostream>
#include <format>

int main()
{
  char ch;
  char* pCh;
  int i;

  std::cout << std::format("Alignment of ch: {} byte(s)",
                           alignof(decltype(ch)))
            << std::endl
            << std::format("Alignment of pCh: {} byte(s)",
                           alignof(decltype(pCh)))
            << std::endl
            << std::format("Alignment of i: {} byte(s)",
                           alignof(decltype(i)))
            << std::endl;
}
Enter fullscreen mode Exit fullscreen mode

We've declared three variables of different types. Let's imagine the compiler places them strictly one after another in the declaration order. Though, in reality, this isn't always the case, and I'll explain why shortly.

When placing data, the compiler follows strict data alignment rules. The language standard guarantees object placement in memory (C++23 sections 6.7.1.1 and 6.7.2.1). Specific alignment values for types depend on the implementation (implementation-defined behavior), or, in other words, on a specific compiler. Most often, they are determined by the data model.

Note. The examples below use the LP64 data model: short is 2 bytes, int is 4 bytes and long, long long, and pointers are 8 bytes.

The ch variable of char type has an alignment of 1 byte and can locate by any address. The pCh pointer with 8-byte alignment will only be placed at an address divisible by 8. The in variable of int type with 4-byte alignment will get a position divisible by 4.

What does this mean in practice? Let's imagine how these variables might be arranged in memory if the compiler placed them sequentially without optimization:

1339_Alignment/image2.png

The compiler placed the variables almost consecutively but inserted 7 unused bytes between ch and pCh. Who was that helpful? The compiler. It automatically inserted unused bytes between the variables. This space between data is called padding bytes. Its aims to align each element to its size, preventing misalignments that could negatively impact performance. Since the content of these padding bytes is implementation-defined, it creates several practical problems—in particular, when using functions like memcmp to compare structures. Such a function analyzes all bytes of the memory object, including uninitialized padding, which can lead to a false negative result even for structures with identical field data.

The total size of the variables is 1 + 8 + 4 = 13 bytes, but they occupy much space in memory due to alignment requirements. It's worth noting that we shouldn't rely on a specific arrangement of this data on the function stack. The compiler can apply various optimizations that don't change the program behavior that we see but may violate the "obvious" order. For example, it can remove unused variables or place used ones in CPU registers.

Now that we've covered the alignment of individual variables and padding (another item for our knowledge base), let's see how this works in more complex cases, such as inside structures.

Alignment in structures

As I mentioned earlier, the alignment mechanism can hold surprises: the same code, depending on the compiler, can be arranged differently in memory. To see this in practice, let's examine program outputs in the following environments:

  1. OS Windows, x86_64 architecture, MSVC compiler;
  2. OS Linux, x86_64 architecture, Clang compiler.

I didn't include the GCC compiler because, in most cases, the result will be the same as for Clang. GCC and Clang in Unix-like systems adhere to the Itanium ABI when forming class layout, while MSVC on Windows uses its own one.

Here's a small structure. What will its size be in each compiler?

struct Example
{
  short int sh;
  char* ptr;
  char symbol;
  long ln;
};
Enter fullscreen mode Exit fullscreen mode

Full code fragment.

Full code fragment.

#include <iostream>
#include <format>

struct Example
{
  short int sh;
  char* ptr;
  char symbol;
  long ln; 
};

int main()
{
  std::cout << "=== Print size and alignment ===" << std::endl
            << std::format("Sizeof of Example: {} byte(s)",
                           sizeof(Example))
            << std::endl
            << std::format("Alignment of Example: {} byte(s)",
                           alignof(Example))
            << std::endl;
}
Enter fullscreen mode Exit fullscreen mode

Output from a program compiled with MSVC.

Output from a program compiled with MSVC.

Compiler Explorer: https://godbolt.org/z/MxKhn5EWr

=== Print size and alignment ===
Sizeof of Example: 24 byte(s)
Alignment of Example: 8 byte(s)
Enter fullscreen mode Exit fullscreen mode

Output from a program compiled with Clang.

Compiler Explorer: https://godbolt.org/z/fq43e9EGM

=== Print size and alignment ===
Sizeof of Example: 32 byte(s)
Alignment of Example: 8 byte(s)
Enter fullscreen mode Exit fullscreen mode

If you started evaluating the Example structure size by simply summing the field sizes, my congrats—that's a mistake :)

In the previous chapter, we talked about padding when working with variables on the stack. The compiler does the same thing for structures. Unlike functions, for structures with standard layout in memory, fields are placed strictly in the order of their declaration—exactly in the sequence they are listed in the code.

How does the compiler arrange fields in memory? The algorithm is straightforward.

  1. Take the first field and place it at offset 0x0. Increase the current offset by the field size. Remember this field alignment.
  2. Take the next field and evaluate its alignment.
    • If the next field alignment is greater than the previous one's, increase the offset (add padding). The increment is calculated using a simple formula: Offset += Align - Offset % Align, where Align is the alignment of the larger field, % is the modulo operation.
    • If the next field alignment is less than or equal to the previous one's, proceed to step 3.
  3. Place the field at the current offset. Increase the current offset by the field size.
  4. Repeat steps 2–4 for all remaining fields.
  5. Remember the resulting offset and call it dsize.
  6. Perform final structure alignment. Take the maximum alignment from all fields and make the offset a multiple of it using the formula from step 2a.
  7. The final class size (size) is the current offset value.

Note. According to the algorithm, we get two values—dsize and size. We'll reveal the meaning of the first one a bit later; for now, we're interested in the final size.

How about trying to arrange the data of the Example structure for Clang and MSVC yourself?

Answer for self-checking.

Placement in Clang:

*** Dumping AST Record Layout
         0 | struct Example
         0 |   short sh
         8 |   char * ptr
        16 |   char symbol
        24 |   long ln
           | [sizeof=32, dsize=32, align=8]
Enter fullscreen mode Exit fullscreen mode

First, the sh field (2 bytes) is placed. The next value to place is the ptr pointer. On the chosen 64-bit architecture, it takes 8 bytes. But let me remind you that data is stored at an address divisible by its size. To comply with this rule, the compiler adds 6 bytes of padding after sh, shifting the start of ptr to the required address. Next, the 1-bytesymbol field is placed without surprises. The next ln field must be aligned to 8 bytes, so the compiler adds another 7 bytes of padding.

If we count all bytes, we get: 2 + 6 + 8 + 1 + 7 + 8 = 32.

Data placement in MSVC:

class Example  size(24):
  +---
 0  | sh
    | <alignment member> (size=6)
 8  | ptr
16  | symbol
    | <alignment member> (size=3)
20  | ln
  +---
Enter fullscreen mode Exit fullscreen mode

Here, the sh field (2 bytes) is also placed first. Next comes the ptr pointer, before which the compiler adds 6 bytes of padding so that it starts at an address divisible by 8. After ptr, the 1-byte symbol field is placed. Next, we see a discrepancy with Clang: the compiler adds 3 bytes of padding before placing the next ln field. It is because MSVC uses the LLP64 memory model, where the size and alignment of the long type is 4 bytes. So, we get: 2 bytes for sh, 6 bytes of padding, 8 bytes for ptr, 1 byte for symbol, 3 bytes of padding, and 4 bytes for ln.

The impact of field order in structures

An inquisitive mind might have guessed that if we reorder the fields in descending order of alignment, we can reduce the impact of padding on the final structure size.

Knowing this, let's rework the structure—rearrange the fields:

struct Example
{
  char* ptr;
  long ln;
  short int sh;
  char symbol;
};
Enter fullscreen mode Exit fullscreen mode

Full code fragment.

#include <iostream>
#include <format>

struct Example
{
  char* ptr;
  long ln;
  short int sh;
  char symbol;
};

int main()
{
  std::cout << "=== Print optimized size and alignment ==="
            << std::endl
            << std::format("Sizeof of Example: {} byte(s)",
                           sizeof(Example))
            << std::endl
            << std::format("Alignment of Example: {} byte(s)",
                           alignof(Example))
            << std::endl;
}
Enter fullscreen mode Exit fullscreen mode

We get the following results:

Output from a program compiled with MSVC.

Compiler Explorer: https://godbolt.org/z/4TYzTW3s8

=== Print optimized size and alignment ===
Sizeof of Example: 16 byte(s)
Alignment of Example: 8 byte(s)
Enter fullscreen mode Exit fullscreen mode

Output from a program compiled with Clang.

Compiler Explorer: https://godbolt.org/z/zn5nGEreq

=== Print optimized size and alignment ===
Sizeof of Example: 24 byte(s)
Alignment of Example: 8 byte(s)
Enter fullscreen mode Exit fullscreen mode

In this order, the compiler doesn't need to add extra padding bytes between fields because each subsequent field is already aligned to an address divisible by its own alignment. For example, after the 8-byte ptr pointer comes the 4-byte ln field, which is automatically aligned to 4 bytes. Next come the 2-byte sh field and the 1-byte symbol field. As a result, the total structure sizes are 16 bytes on MSVC and 24 bytes on Clang.

So, should we always arrange fields in this order? The answer depends on how many instances of such structures are created.

  • When only a few instances of the structure are created, we can neglect this optimization if it breaks a logical order.
  • If the number of created instances is high (millions or more), the optimization may be justified.

Also, we should remember about the ABI: if the field order is important for our application, we can't change it, as this would break backward compatibility.

Empty structures

In C++, a class/structure is considered empty if it contains no non-static data members. Such structures simply have no members to occupy memory. However, we know that every object must have its own address in memory, so the compiler has to allocate at least 1 byte even for empty structures.

Fun fact: in the C language, empty structures are not allowed; otherwise, the behavior is undefined (C23 §6.7.3.2/10).

When an empty structure becomes a member of another structure, it also occupies at least 1 byte. However, due to alignment requirements, the compiler is often forced to add additional padding bytes. What do we get in the end? The empty structure, which technically might not occupy space, actually increases the size of the owner structure significantly more than by its own single byte.

To solve this problem, C++20 introduced the [[no_unique_address]] attribute. It tells the compiler that the field doesn't necessarily need to have a unique address. If the field is empty, the compiler can optimize its placement and not allocate separate memory for it.

When we apply this attribute to an empty field, we allow the compiler to place it in the same bytes that are usually used for alignment padding of other structure fields. Look at the example:

struct Empty {};

struct Normal
{
  Empty e;
  int x;
};

struct Optimized
{
  [[no_unique_address]] Empty e;
  int x;
};
Enter fullscreen mode Exit fullscreen mode

Full code fragment.

#include <iostream>
#include <format>

struct Empty {};

struct Normal
{
  Empty e;
  int x;
};

struct Optimized
{
  [[no_unique_address]] Empty e;
  int x;
};

int main()
{
  std::cout << "=== Empty sub-object optimization ===" << std::endl
            << std::format("Empty size = {} byte(s)",
                           sizeof(Empty))
            << std::endl
            << std::format("Empty alignment = {} byte(s)",
                           alignof(Empty))
            << std::endl
            << std::format("Normal size = {} byte(s)",
                           sizeof(Normal))
            << std::endl
            << std::format("Normal alignment = {} byte(s)",
                           alignof(Normal))
            << std::endl
            << std::format("Optimized size = {} byte(s)",
                           sizeof(Optimized))
            << std::endl
            << std::format("Optimized alignment = {} byte(s)",
                           alignof(Optimized))
            << std::endl;
}
Enter fullscreen mode Exit fullscreen mode

Output from a program compiled with MSVC.

Compiler Explorer: https://godbolt.org/z/GsGc4GYxe

=== Empty sub-object optimization ===
Empty size = 1 byte(s)
Empty alignment = 1 byte(s)
Normal size = 8 byte(s)
Normal alignment = 4 byte(s)
Optimized size = 8 byte(s)
Optimized alignment = 4 byte(s)
Enter fullscreen mode Exit fullscreen mode

Output from a program compiled with Clang.

Compiler Explorer: https://godbolt.org/z/fMedjKGhf

=== Empty sub-object optimization ===
Empty size = 1 byte(s)
Empty alignment = 1 byte(s)
Normal size = 8 byte(s)
Normal alignment = 4 byte(s)
Optimized size = 4 byte(s)
Optimized alignment = 4 byte(s)
Enter fullscreen mode Exit fullscreen mode

Interesting fact: in the MSVC compiler, this optimization doesn't work by default, and the structure size will be 8 bytes. Microsoft has so far decided not to break its ABI. However, the optimization can be enabled using the [[msvc::no_unique_address]] attribute.

Bit fields

Bit fields allow packing several integral-type fields into one or more bytes, saving memory. For example, this is a good way to optimize the placement of a collection of boolean flags when their number in a structure grows (more than 8).

Let's look at this structure as an example:

// since C++20
struct Example
{
  unsigned char dummy1;
  unsigned char flag1 : 1;
  unsigned char flag2 : 1;
  unsigned char flag3 : 1;
  unsigned char flag4 : 1;
  unsigned char dummy2;
};
Enter fullscreen mode Exit fullscreen mode

The dummy1 and dummy2 fields serve auxiliary purposes: we can't take the address of a bit field.

Full code fragment

#include <iostream>
#include <format>

struct Example
{
  unsigned char dummy1;
  unsigned char flag1 : 1;
  unsigned char flag2 : 1;
  unsigned char flag3 : 1;
  unsigned char flag4 : 1;
  unsigned char dummy2;
};

int main()
{
  Example obj;
  std::cout << "=== Bit-fields ===" << std::endl
            << std::format("Example size without "
                           "dummy data members: {} byte(s)",
                           sizeof(Example) - 2 * sizeof(unsigned char))
            << std::endl
            << std::format("Example alignment: {} byte(s)",
                           alignof(Example))
            << std::endl << std::endl
            << "=== Addresses ===" << std::endl
            << std::format("Example flag1 address: 0x{:p}",
                           static_cast<const void *>(
                             &obj.dummy1 + sizeof(Example::dummy1)
                           ))
            << std::endl
            << std::format("Example dummy2 address: 0x{:p}",
                           static_cast<const void *>(&obj.dummy2))
            << std::endl;
}
Enter fullscreen mode Exit fullscreen mode

Output from a program compiled with MSVC.

Compiler Explorer: https://godbolt.org/z/1K73x9c1P

=== Bit-fields ===
Example size without dummy data members: 1 byte(s)
Example alignment: 1 byte(s)

=== Addresses ===
Example flag1 address: 0x0x7f46affd01
Example dummy2 address: 0x0x7f46affd02
Enter fullscreen mode Exit fullscreen mode

Output from a program compiled with Clang.

Compiler Explorer: https://godbolt.org/z/KM837qbxa

=== Bit-fields ===
Example size without dummy data members: 1 byte(s)
Example alignment: 1 byte(s)

=== Addresses ===
Example flag1 address: 0x0x7ffcf4de63fe
Example dummy2 address: 0x0x7ffcf4de63ff
Enter fullscreen mode Exit fullscreen mode

At first glance, all bit fields should occupy only 4 bits. However, the structure size excluding our auxiliary fields is 1 byte. Why? The answer is simple: the minimum storage unit is a byte, which stores N bits of information—it's defined by the implementation via the CHAR_BIT macro. On the platforms used for experiments, 1 byte stores 8 bits. We used only four bits, so the compiler will also insert another 4 bits of padding to fully fill the storage unit.

Strictly speaking, the language standard fully leaves the method of physical placement of bit fields to the implementation—the ABI of a specific platform and compiler. The compiler can pack the bits into one storage unit or create its own storage unit for each bit field. The compiler also chooses from which end of the storage unit to start (little/big endian).

However, some compilers are similar: if all bit fields in a structure are declared with the same underlying type (ignoring signed and unsigned types), then compilers most often pack them sequentially, without extra padding, within storage units of that type.

There are also some differences in compiler implementations.

  • When changing the underlying type of a bit field, the compiler can either implicitly end the previous storage unit (MSVC) or continue filling further (GCC, Clang).
  • When declaring a bit field whose size exceeds the capacity of its underlying type, all additional bits are considered padding bits. Clang and GCC adhere to this behavior but issue a warning. MSVC, in this case, considers the code erroneous and refuses to compile it.

The '#pragma pack' directive

In some cases we need to control alignment manually. For this, we can use the #pragma pack directive, which tells the compiler how many bytes to use to align fields instead of the standard alignment. Look at the example:

struct NormalStruct
{
  char a;
  int b;
  double c;
};

#pragma pack(push, 1)
struct PackedStruct
{
  char a;
  int b;
  double c;
};
#pragma pack(pop)
Enter fullscreen mode Exit fullscreen mode

Full code fragment.

#include <iostream>
#include <format>

struct NormalStruct
{
  char a;
  int b;
  double c;
};

#pragma pack(push, 1)
struct PackedStruct
{
  char a;
  int b;
  double c;
};
#pragma pack(pop)

int main()
{
  std::cout << "=== Structure Packing ===" << std::endl
            << std::format("NormalStruct size: {} byte(s)",
                           sizeof(NormalStruct))
            << std::endl
            << std::format("NormalStruct alignment: {} byte(s)",
                           alignof(NormalStruct))
            << std::endl
            << std::format("PackedStruct size: {} byte(s)",
                           sizeof(PackedStruct))
            << std::endl
            << std::format("PackedStruct alignment: {} byte(s)",
                           alignof(PackedStruct))
            << std::endl; 
  return 0;
}
Enter fullscreen mode Exit fullscreen mode

Output from a program compiled with MSVC.

Compiler Explorer: https://godbolt.org/z/ahnEnPPjz

=== Structure Packing ===
NormalStruct size: 16 byte(s)
NormalStruct alignment: 8 byte(s)
PackedStruct size: 13 byte(s)
PackedStruct alignment: 1 byte(s)
Enter fullscreen mode Exit fullscreen mode

Output from a program compiled with Clang.

Compiler Explorer: https://godbolt.org/z/3h37Erbfo

=== Structure Packing ===
NormalStruct size: 16 byte(s)
NormalStruct alignment: 8 byte(s)
PackedStruct size: 13 byte(s)
PackedStruct alignment: 1 byte(s)
Enter fullscreen mode Exit fullscreen mode

The #pragma pack(1) directive radically changes the rules, forcing the compiler to pack data as tightly as possible. In PackedStruct, fields are placed strictly sequentially without any alignment. This approach saves memory (exactly 13 bytes), but it comes at a serious cost—unaligned data access:

  • If the CPU architecture allows access to such data, read and write operations may significantly slow down. An example of such an architecture is x86_64.
  • The processor may generate a hardware disruption when accessing unaligned data. An example of such an architecture is ARM.

__declspec(align), __attribute__((aligned(N))), alignas, and alignof

As we already know, the order of placement is quite important in structs/classes. But what if we need to set the alignment ourselves? For example, it is sometimes required when working with SIMD: data must be aligned on a 32-byte boundary.

For this, compilers provide special attributes and/or specifiers. Historically, let's start with __declspec(align(N)) in MSVC and __attribute__((aligned(N))) in Clang and GCC—these are compiler-specific extensions that allow explicit control over memory alignment requirements:

// MSVC
__declspec(align(32)) struct SIMD_Data_MSVC
{
  double d1;
  double d2;
  double d3;
  double d4;
};

// Clang and GCC
struct SIMD_DATA_Clang_GCC
{
  double d1;
  double d2;
  double d3;
  double d4;
} __attribute__((aligned(32)));
Enter fullscreen mode Exit fullscreen mode

Syntactically, these attributes differ: in MSVC, the __declspec(align(N)) specifier is placed before the structure or variable declaration; in Clang and GCC, the __attribute__((aligned(N))) attribute is used, which typically follows the structure name or type in the declaration. Attributes can be used both for packing and for setting stricter alignment requirements.

The difference in syntax is a common source of errors when porting code between platforms. This problem was solved in the C11 and C++11 standards. New tools for working with alignment appeared—the alignof operator and the alignas specifier:

  • The alignof operator returns the alignment of the passed type at compile time. For example,alignof(char) is always 1, since a byte can be placed at any address.
  • The alignas specifier allows explicit setting of alignment requirements for variables, structure fields, or the types. It can take either an integer value or another type. For example, alignas(32) double value; guarantees that the value variable will be placed at an address divisible by 32 bytes. An important difference from compiler-specific attributes is that the alignas operator doesn't allow packing. An attempt to reduce the required alignment will result in a compilation error.

Static analyzers and alignment

Often, it's quite difficult to manually control all the nuances of alignment. This is where static analysis comes to the rescue, which can catch various code defects related to alignment. For example, here's what PVS-Studio can highlight.

  • V802 from the optimization group focuses on finding structures where size can be reduced simply by changing the order of field declaration for denser data packing.
  • V1032 signals situations where a pointer cast may lead to an access via an unaligned address.
  • V1103 draws attention to errors in byte-by-byte comparison of structures that contain random values in paddings.
  • V2666 from the MISRA group tracks the consistency of alignment specifiers. If you specified alignas for an object, then all other redeclarations must use the same alignment.

Let's look at a couple of real-world errors related to alignment that we found in open-source projects.

FreeCAD project

template <int N>
void TRational<N>::ConvertTo (double& rdValue) const
{
  assert(m_kDenom != 0);
  if (m_kNumer == 0)
  {
    rdValue = 0.0;
    return;
  }

  unsigned int auiResult[2];
  ....
  rdValue = *(double*)auiResult;
  ....
}
Enter fullscreen mode Exit fullscreen mode

Here, the line rdValue = *(double*)auiResult; is that insidious place. The author tries to cast the value to a double type by reading two unsigned int following each other. Such reading is not directly possible, so the author resorts to pointer conversion.

This option is non-working and dangerous. The problem lies in violating alignment requirements. The auiResult address is aligned for the unsigned int type (4 bytes), while the double type (8 bytes) requires stricter alignment. Reading a variable at an address not divisible by its type's requirement leads to undefined behavior.

Here's the PVS-Studio warning: V1032. The pointer 'auiResult' is cast to a more strictly aligned pointer type. Wm4TRational.inl

TDengine project

typedef struct STreeNode {
  int32_t index;
  void   *pData;  // TODO remove it?
} STreeNode;

int32_t tMergeTreeAdjust(SMultiwayMergeTreeInfo* pTree, int32_t idx) {
  ....
  STreeNode kLeaf = pTree->pNode[idx];
  ....
  if (memcmp(&kLeaf, &pTree->pNode[1], sizeof(kLeaf)) != 0) {
  ....
}
Enter fullscreen mode Exit fullscreen mode

Here, the error is in comparing objects of the STreeNode structure. Since the structure contains fields of different sizes (int32_t and the void* pointer), the compiler adds 4 bytes of padding after index to ensure pointer alignment. To compare objects, the memcmp function is used, which compares the entire byte representation, including padding. As we remember, implementation defines how padding bytes are filled. This can lead to unexpected results. For example, even if the index and pData fields match, objects may be considered unequal due to random data in the empty bytes the compiler added for alignment.

The PVS-Studio warning: V1103 The values of padding bytes are unspecified. Comparing objects with padding using 'memcmp' may lead to unexpected result. tlosertree.c 127

Conclusion

So, the mystery is revealed! Data alignment is not an enemy but an ally whose rules, once understood, allow you to write fast and stable code. Armed with the knowledge from this article, you can turn mysterious crashes and slowdowns into solvable tasks. Your code is now ready to meet any "hardware". However, in this extensive article, I deliberately omitted several topics: inheritance, POD structures, multiple inheritance, virtual tables, virtual base classes, RTTI, etc. To be continued... :)

To make sure all intricacies of alignment are taken care of in your project, use PVS-Studio static analyzer. It will help check the code for non-obvious errors related to memory management and will become your assistant in writing portable and reliable code.

Top comments (0)