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:
- 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)
- 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.
- Alignment that is not power of two is ignored
#pragma pack(push, 3) #pragma pack(pop)
- 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)
- __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.
No comments yet.
-
Recent
- OpenGL hardware acceleration through remote X11 SSH connection
- GDB: How do I set current source file for list and break commands
- How To Create and Seed a Torrent (Ubuntu server, Transmission)
- GIT TF: Undo shallow pull and pull squashed changeset
- Lynx on Windows 7 and lynx_bookmarks.html file problem
- Old MacBook Overheating and Installation of Mac OS 10.4 on New Hard Drives
- Memory Alignment Of Structures and Classes in C++
- Align label and input vertically
- Google Test Framework and Visual Studio 2010
- Convex Hull
- Run a bash script with sudo, nohup and in the background
- Contact database with web interface – EVPO Members
-
Links
-
Archives
- March 2017 (1)
- May 2015 (1)
- January 2015 (1)
- November 2014 (1)
- October 2014 (1)
- March 2014 (1)
- January 2014 (1)
- June 2013 (1)
- May 2013 (2)
- February 2012 (2)
- October 2010 (1)
- February 2010 (1)
-
Categories
-
RSS
Entries RSS
Comments RSS
Leave a Reply