Consider the following structure:
Struct foo
{
Char c1;
Short s;
Char c2;
Int I;
};
Assume that the members of this structure are in a compact arrangement in the memory. If the c1 address is 0, the s address should be 1, and the c2 address is 3, the address of I is 4. That is
C1 00000000, s 00000001, c2 00000003, I 00000004.
However, we write a simple program in Visual c/c ++ 6:
Struct foo;
Printf ("c1% p, s % p, c2 % p, I % p/n ",
(Unsigned int) (void *) & a. c1-(unsigned int) (void *) &,
(Unsigned int) (void *) & a. s-(unsigned int) (void *) &,
(Unsigned int) (void *) & a. c2-(unsigned int) (void *) &,
(Unsigned int) (void *) & a. I-(unsigned int) (void *) & );
Run and output:
C1 00000000, s 00000002, c2 00000004, I 00000008.
Why? This is a problem caused by memory alignment.
Why memory alignment
The following content is excerpted from Intel Architecture 32 Manual.
Words, dual words, and four words do not need to be aligned in the memory on the natural boundary. (For words, double words, and four words, the natural boundary is an even number of addresses, which can be divided by four, and an address that can be divided by eight .)
In any case, data structures (especially stacks) should be aligned as much as possible on natural boundaries to improve program performance. The reason is that the processor needs to perform two memory accesses to access non-alignment memory; however, alignment memory access only needs one access.
A single-or dual-character operand spans the 4-byte boundary, or a four-character operand spans the 8-byte boundary, which is considered not aligned and thus requires two bus cycles to access the memory. The starting address of a word is odd, but it does not span the word boundary. It is considered to be aligned and can be accessed in a bus cycle.
Some operations require memory operations to be aligned on the natural boundary. If the operands are not aligned, these commands generate a general protection exception (# GP ). The natural boundary of dual words is the address that can be divided by 16 characters. Other double-byte commands allow non-alignment access (without common protection exceptions). However, additional memory bus cycles are required to access non-alignment data in the memory.
Processing of memory alignment by the compiler
By default, the C/C ++ compiler performs memory alignment for the member data in the structure and stack by default. Therefore, the output of the above program becomes:
C1 00000000, s 00000002, C2 00000004, I 00000008.
The compiler shifts unaligned members to the backend, alignment each Member to the natural boundary, which leads to a larger size of the entire structure. Although it sacrifices a little space (there are holes between members), it improves performance.
For this reason, we cannot assert sizeof (FOO) = 8. In this example, sizeof (FOO) = 12.
How to avoid the impact of memory alignment
So can we both improve performance and save some space? TIPS. For example, we can change the above structure:
Struct bar
{
Char c1;
Char c2;
Short s;
Int I;
};
In this way, each member is aligned on its natural boundary, thus avoiding the automatic alignment of the compiler. In this example, sizeof (bar) = 8.
This technique plays an important role, especially when the structure is provided to third-party developers as part of the API. Third-party developers may change the default alignment options of the compiler, causing this structure to use some Alignment Method in your released DLL, while where third-party developers use another Alignment Method. This will cause major problems.
For example, in the foo structure, our DLL uses the default alignment option, alignment is
C1 00000000, s 00000002, c2 00000004, I 00000008, and sizeof (foo) = 12.
The third party closes the alignment option, resulting in
C1 00000000, s 00000001, c2 00000003, I 00000004, and sizeof (foo) = 8.
How to Use alignment options in c/c ++
The compilation options in vc6 include/Zp [1 | 2 | 4 | 8 | 16]./Zp1 indicates alignment at the boundary of 1 byte, and Zpn indicates alignment at the boundary of n Bytes. The n-byte boundary alignment means that the address of a member must be arranged on an integer multiple address of the member size or an integer multiple address of n, and the minimum values of the two must be obtained. That is:
Min (sizeof (member), n)
In fact, the one-byte boundary alignment means that there is no holes between the structure members.
The/Zpn option is applied to the entire project and affects all structures involved in compilation.
To use this option, you can open the project properties page in vc6, c/c ++ page, select the Code Generation category, and select in Struct member alignment.
To use alignment options for some schema definitions, use the # pragma pack compilation command. Command syntax:
# Pragma pack ([show] | [push | pop] [, identifier], n)
The meaning is the same as that of the/Zpn option. For example:
# Pragma pack (1)
Struct foo_pack
{
Char c1;
Short s;
Char c2;
Int I;
};
# Pragma pack ()
Stack memory alignment
We can observe that the stack alignment in vc6 is not affected by the alignment option of the structure member. (There are two things ). It always maintains alignment and alignment on the 4-byte boundary.
Verification Code
# Include <stdio. h>
Struct foo
{
Char c1;
Short s;
Char c2;
Int I;
};
Struct bar
{
Char c1;
Char c2;
Short s;
Int I;
};
# Pragma pack (1)
Struct foo_pack
{
Char c1;
Short s;
Char c2;
Int I;
};
# Pragma pack ()
Int main (int argc, char * argv [])
{
Char c1;
Short s;
Char c2;
Int I;
Struct foo;
Struct bar B;
Struct foo_pack p;
Printf ("stack c1 % p, s % p, c2 % p, I % p/n ",
(Unsigned int) (void *) & c1-(unsigned int) (void *) & I,
(Unsigned int) (void *) & s-(unsigned int) (void *) & I,
(Unsigned int) (void *) & c2-(unsigned int) (void *) & I,
(Unsigned int) (void *) & I-(unsigned int) (void *) & I );
Printf ("struct foo c1 % p, s % p, c2 % p, I % p/n ",
(Unsigned int) (void *) & a. c1-(unsigned int) (void *) &,
(Unsigned int) (void *) & a. s-(unsigned int) (void *) &,
(Unsigned int) (void *) & a. c2-(unsigned int) (void *) &,
(Unsigned int) (void *) & a. I-(unsigned int) (void *) & );
Printf ("struct bar c1 % p, c2 % p, s % p, I % p/n ",
(Unsigned INT) (void *) & B. C1-(unsigned INT) (void *) & B,
(Unsigned INT) (void *) & B. C2-(unsigned INT) (void *) & B,
(Unsigned INT) (void *) & B. S-(unsigned INT) (void *) & B,
(Unsigned INT) (void *) & B. I-(unsigned INT) (void *) & B );
Printf ("struct foo_pack C1 % P, S % P, C2 % P, I % P/N ",
(Unsigned INT) (void *) & P. C1-(unsigned INT) (void *) & P,
(Unsigned INT) (void *) & p.s-(unsigned INT) (void *) & P,
(Unsigned INT) (void *) & P. C2-(unsigned INT) (void *) & P,
(Unsigned int) (void *) & p. I-(unsigned int) (void *) & p );
Printf ("sizeof foo is % d/n", sizeof (foo ));
Printf ("sizeof bar is % d/n", sizeof (bar ));
Printf ("sizeof foo_pack is % d/n", sizeof (foo_pack ));
Return 0;
}