Alignment in C++ (ENG)

Object Memory Location

The operating system provides us with an abstract view of how real memory works, giving us a virtual contiguous sequence of bytes to use. When an object is created, the system needs to allocate the object on the stack or heap memory, depending on how it is created, and you get a memory address to manipulate this object. This address is called the memory location.

(By the way, if you find a class with no data members in it, would it have zero memory cost? No, because it needs at least one byte to be instantiated in memory. Otherwise, chaos would ensue since you wouldn’t be able to obtain a unique address for that instance.)

class no_data_member{
	void printName(){
		std::cout << "no_data_member" << std::endl;
	}
};
int main(){
	no_data_member i; // occipies 1 byte of memory
}

At this location, you need some contiguous space to store the object, so every object will have a size property, which you can get through the sizeof operator. And also, every complete object type has an alignment requirement property. This alignment is what we are going to talk about.

Alignment and Alignment Optimization

Alignment is an integer value of the size_t type representing the number of bytes between successive addresses at which objects of a type can be allocated. A valid alignment value is a non-negative integer that is a power of two. For example:

#include <stdio.h>
struct A{
	char c;  // size: 1, alignment: 1
	long l;  // size: 8, alignment: 8
	short s; // size: 2, alignment: 2
	int i;   // size: 4, alignment: 4
};

int main(){
	printf("sizeof struct A is: %ld\n", sizeof(A));
	// The alignment of a type can be queried with the `alignof` operator.
	printf("alignof struct A is: %ld\n", alignof(A));
}

When we add up the struct size, we might expect the struct size to be 13 bytes, right? Wrong! We got 24 bytes instead. And we have an alignment of struct A being 8. What the heck is that?

The alignment requirement of the struct is determined by the member with the strictest (largest) alignment, which is long l with an 8-byte alignment. Therefore, the overall alignment of struct A is 8 bytes.

To satisfy the alignment requirement of all members of the struct, the compiler will insert padding after some of its members. The grey area in the picture below represents those inserted padding.

align_ex.png

In order to optimize the memory layout of the struct, we could:

#include <stdio.h>
struct A{
	char c;  // size: 1, alignment: 1
	long l;  // size: 8, alignment: 8
	short s; // size: 2, alignment: 2
	int i;   // size: 4, alignment: 4
};
// same member, but smaller size
struct B{
	char c;  // size: 1, alignment: 1
	short s; // size: 2, alignment: 2
	int i;   // size: 4, alignment: 4
	long l;  // size: 8, alignment: 8	
};

int main(){
	printf("sizeof struct A is: %ld\n", sizeof(A));
	// The alignment of a type can be queried with the `alignof` operator.
	printf("alignof struct A is: %ld\n", alignof(A));
	printf("sizeof struct B is: %ld\n", sizeof(B));
	// The alignment of a type can be queried with the `alignof` operator.
	printf("alignof struct B is: %ld\n", alignof(B));
}

We see the struct B only used 16 bytes of memory. What happened here? We reduced the need for padding by arranging its members in a more efficient order. The padding in this struct is only 1 byte after char c like what I draw fellows:

align_ex2.png

By optimizing the member order, we minimized the padding required to satisfy alignment requirements, resulting in a more compact memory layout for the struct.