Evgeny Pokhilko's Weblog

Programmer's den

Memory Alignment Of Structures and Classes in C++


Everything below was tested in Visual Studio 2012 Win32. The code for this post is here

Introduction

The memory to store a particular structure or object is split into blocks of determined size. This size is called alignment.

The C++ standard requires that members are stored in memory in the order of declaration. Multiple consecutive members can be “packed” in one block. If the next member doesn’t fit into the remained bytes of the block, it’s saved into the following block. Those unused bytes in the previous block are called “padding”.

Normally the alignment is determined by the compiler for each structure type. In a simple scenario where the structure doesn’t contain other structures, the alignment is the size of the largest type stored in the structure. However, in some environments a simple type can have alignment requirement that is smaller than the size of the type. For example double can have alignment of 4. So it would be better to say that alignment of a structure is the maximum alignment of its contained types. Alignment is only allowed to be power of two by the standard.

Examples:

1.

struct Alignment4
{
	char a;
	char b; 
	//char[2] - padding
	int i; // max alignment 4
Alignment4():a(1),b(2),i(3){} 
}; // size is 8

Alignment requirement for int is 4 and char 1. The maximum is 4 so 4 will be the alignment of Alignment4 structure. sizeof(Alignment4)==8

Memory:  01 02 cd cd 03 00 00 00

cd is the special value used for padding in this case (compiled in debug configuration).  (Note that 03 is the first byte of the integer indicating that we see little endian – http://en.wikipedia.org/wiki/Endianness).

2.

struct Alignment8
{
	int i; 
	//char[4]
	double d; // max alignment 8
	short s;
	//char[6]
	Alignment8():i(1), d(2), s(3){}
}; // size is 24

Alignments of the types are the following: int 4, double 8, short 2. The maximum is 8 so the alignment of the structure is 8.

Memory: 01 00 00 00 cd cd cd cd 00 00 00 00 00 00 00 40 03 00 cd cd cd cd cd cd

Note: 00 00 00 00 00 00 00 04 is how the double value of 2 is represented in memory.

The compiler outputs information about padding as below when /Wall (WarningLevel – EnableAllWarnings in C/C++ => General) switch is used:

1>size_test.cpp(16): warning C4820: ‘Alignment4’ : ‘2’ bytes padding added after data member ‘Alignment4::b’

1>size_test.cpp(24): warning C4820: ‘Alignment8’ : ‘4’ bytes padding added after data member ‘Alignment8::i’

1>size_test.cpp(28): warning C4820: ‘Alignment8’ : ‘6’ bytes padding added after data member ‘Alignment8::s’

1>size_test.cpp(35): warning C4820: ‘BetterAlignment8’ : ‘2’ bytes padding added after data member ‘BetterAlignment8::s’

1>size_test.cpp(43): warning C4820: ‘Size8’ : ‘6’ bytes padding added after data member ‘Size8::b’

1>size_test.cpp(46): warning C4820: ‘Size8’ : ‘7’ bytes padding added after data member ‘Size8::c’

1>size_test.cpp(68): warning C4820: ‘Size6’ : ‘3’ bytes padding added after data member ‘Size6::a’

Optimization

We only need 14 bytes to store Alignment8 but it takes 24. The order of the members is the problem. The block is 8 bytes and when we pack d, it has to start from the next block as there is only 4 bytes left in the current block, which is not enough for d. i and s can fit into one block but they are separated by d. BetterAlignment8 (see below) takes only 16 bytes.

struct BetterAlignment8
{
	int i;
	short s;
	//char[2]
	double d; // max alignment 8
	BetterAlignment8():i(1), d(2), s(3){}
}; // size is 16

Memory:  01 00 00 00 03 00 cd cd 00 00 00 00 00 00 00 40

This will reduce the memory consumption and possibly improve the performance as more elements will fit into the CPU cache.

Alignment control

There are several ways you can tell the compiler to change the default alignment:

  1. Across the project /ZpN where N can be 1, 2, 4, 8, 16.
    StructMemberAlignment
  1. Specify alignment within code range of a module.

In this example the alignment of Size7 and Size5 will be 1.

#pragma pack(push, 1)
struct Size7
{
	char a;
	char b;
	double i;
	char c;
	S7():a(1), b(2), c(3), i(0){}
};

struct Size5
{
	char a;
	int i;
};
#pragma pack(pop)

  1. Specify alignment per structure type.
struct __declspec(align(8)) Size6
{
	char a;
	int i;
	Size6():a(1), i(2){}
};

You can even control alignment per member http://msdn.microsoft.com/en-us/library/83ythb65.aspx.

Compiler oddities and limitations

In Visual Studio 2012 if you specify alignment that the compiler doesn’t accept in a particular case, it will be ignored without a warning.  Everything below also has effect in Visual Studio 2012.

  1. Alignment that is not power of two is ignored
#pragma pack(push, 3)

#pragma pack(pop)
  1. Pragma pack instruction is ignored if the alignment is greater than the default alignment for the structure:
#pragma pack(push, 8)
// default alignment is 4. If default_alignment > pragma_alignment, pragma is ignored.
struct Alignment4
{
	char a;
	int i;
	Alignment4():a(1), i(2){}
}; // alignment is still 4
#pragma pack(pop)
  1. __declspec instruction is opposite to #pragma. It is ignored if the alignment is less than the default alignment.
// if declspec_alignment <= default_alignment then the instruction is ignored
struct __declspec(align(2)) Size8a
{
char a;
	int i;
	Size8a():a(1), i(2){}
}; // alignment is still 4

Asserting alignment

If you have specific requirements for the alignment in your piece of code and you don’t know how it will be compiled in the future, it’s always better to assert it in the code. C++11 or TR1 are required.

#include <type_traits>
Struct MyStructure
{
	Char c;
	Int I;
};
Static_assert(std::alignment_of(MyStructure)::value == 4, “Alignment of MyStructure must be 4”);

If the alignment is not 4, there will be a compilation error.

Alignment conflicts

This is the reason why programmers start to learn about alignment most often. If you declare the same structure in two different modules with different alignment parameters, you will get a conflict. See the example below:

module01.cpp

struct Size5
{
	char a;
	int i;
};
Size5 global_size_5;

module02.cpp

#pragma pack(push, 1)
struct Size5
{
	char a;
	int i;
};
#pragma pack(pop)
extern Size5 global_size_5;

You will get this warning:

LINK : warning C4742: ‘struct Size5 global_size_5’ has different alignment in ‘D:\dhome\size_test\size_test.cpp’ and ‘D:\dhome\size_test\notepad.cpp’: 1 and 4

More about C4742 – http://msdn.microsoft.com/en-us/library/k334t9xx(v=vs.110).aspx

However, it doesn’t always happen. If you return a variable from another module by reference or pointer, there will be no warnings.

Conclusion

If the system you are working on demands high performance or low memory consumption, you may want to change the default alignment. It should be measured and tested of course. Sometimes you have to deal with alignment even when you don’t expect. For example the Microsoft code generator for COM proxies inserts alignment control directives. It can cause issues that you need to be prepared to understand. So alignment is worth keeping an eye on.

January 25, 2014 - Posted by | C++

No comments yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: