When a structural type is defined in C, is its size equal to the sum of the fields (Field) size? How will the compiler places these fields in memory? What is the requirements for ANSI C on the memory layout of the structure? And can our procedure rely on this layout? These issues may be a bit blurred for many friends, then this article tries to explore the secret behind them.
First, at least one thing can be affirmed, that is, the ANSI C guarantees that the location of the fields in the structure in memory is increasing with their declaration order, and the first address of the first field is equal to the first address of the entire structure instance . For example, there is such a structure: struct vector {int X, y, z;} s; int * p, * q, * r; struct vector * ps; p = & S.X; q= & s.y; r = & s .z; ps = & s;
Assert (P At this time, friends may ask: "Is the standard specify that adjacent fields are also adjacent in memory?" Oh, sorry, ANSI C did not make guarantees, your program should not rely on this assumption at any time. Is this meant that we will never have a clearer and more accurate structure of the structure? Oh, of course not. However, let us temporarily pick up from this question, pay attention to another important issue ---- memory alignment. Many actual computer systems have restrictions on the location where basic type data is stored in memory, which requires the value of the first address of these data is a multiple of a number K (usually 4 or 8), which is the so-called memory alignment And this K is called Alignment Modulus. When the alignment of the alignment mode of one type S is more than 1, we are called the alignment of type s than the T strong (strict), and the T is weak (loose) ). This mandatory requirement simplifies the design of the transmission system between the processor and the memory, two can improve the speed of reading data. For example, in such a processor, it starts from an 8x address each time it reads and writes memory, reads or writes 8 bytes of data, if the software ensures that the Double type data is 8 times. The address starts, then read or write a Double type data only one memory operation. Otherwise, we may need two memory operations to complete this action, because the data may just across the 8-byte memory blocks that meet the alignment requirements. Some processors may be erroneous in the case where data does not meet alignment requirements, but the IA32 architecture of Intel can work correctly regardless of whether the data is aligned. But Intel advises everyone, if you want to improve performance, all program data should be as aligned as much as possible. The Microsoft C compiler (Cl.exe for 80x86) under the Win32 platform uses the following alignment rules: Any alignment of the basic data type T is the size of T, ie SizeOf (T). For example, for the Double type (8 bytes), the address of this type of data is always 8 times, while the char type data (1 byte) can start from any address. The GCC under Linux is another set of rules (found in the information, not verified, if you are wrong): Any 2-byte size (including single-byte?) Data type (such as short) alignment The modulus is 2, while all other data types of more than 2 bytes (such as long, double) are aligned with 4 is aligned. Now go back to our concern Struct. ANSI C specifies the size of a structural type as the size of all of its fields and the fill area between fields or field tailings. Ok? Fill area? Yes, this is to give the structure field to satisfy the amount of memory to the structure to the structure. So what is the reign of the structure itself? Yes, ANSI C standard specifies that the structure of structural types cannot be more stringent than it requires the most stringent, but this is not forced, VC7.1 is just as strict. Let's take an example (the following environment of all the tests is Intel Celeron 2.4G Win2000 Pro VC7.1, the memory alignment option is "default", ie not specified / zp and / pack option): typedef struct ms1 {char A ; Int b;} MS1; Suppose the MS1 is in the following mode (all the memory addresses in this document are incremented from left to right): ____________________________ | | | ------------- ------------ BYTES: 1 4 Since there is a stronger alignment request in MS1 is a B field (int), according to the alignment rules of the compiler, the ANSI C standard, the first address of the MS1 object must be a multiple of 4 (Alignment of the INT type). So can the B field in the above memory layout meet the INT type alignment requirements? Well, of course I can't. If you are a compiler, how do you clever arrangements to meet the CPU? Oh, after 1 milliseconds of hard thinking, you must have drawn as follows: ______________________________________ | | /// | | | | | | /// | | ----------------------- -------------- BYTES: 1 3 4 This scheme has allocated three fills in A and B, so when the entire STRUCT object first address meets 4 bytes of alignment requirements, the B field must also meet the 4-byte alignment of the INT type. . So SizeOf (MS1) is obviously 8, and the offset of the B field relative to the structure of the structure is 4. Very good, right? Now let's exchange the fields in the MS1: Typedef struct ms2 {int a; char b;} ms2; Maybe you think that MS2 is simpler than MS1, its layout should be _______________________ | | | | A | B | | | --------------------- BYTES: 4 1 Because the MS2 object also needs to satisfy the 4-byte alignment, the address of this is equal to the first address of the structure, so it must also be 4 bytes aligned. Well, it makes sense, but it is not comprehensive. Let us consider what problems with the array that defines a MS2 type. C standard guarantees that the size of any type (including custom structural types) is equal to the size of a separate type of data by the number of array elements. In other words, there is no gap between arrays each element. According to the above scheme, a MS2 array Array layout is: | <- array [1] -> | <- array [2] -> | <- array [3] ..... ________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________ A | B | A | B | ............. | | | | | ----------------------- ----------------------------------- BYTES: 4 1 4 1 When the array head address is 4-byte alignment, Array [1] .a is also 4 bytes, but Array [2] .a? Array [3] .a ... It can be seen that this scenario does not allow fields of all elements in the array to meet the fields in the array, must be modified as the following form: __________________________________ | | | /// | | | | | | // | ----------------------- ---------- BYTES: 4 1 3 Now whether it is defined a separate MS2 variable or an MS2 array, all fields of all elements can be guaranteed to meet alignment. So, SIZEOF (MS2) is still 8, and the offset of A is 0, and the offset of B is 4. Ok, now you have mastered the basic guidelines of the structure of the structure in vivo, try to analyze a slightly complex point. Typedef struct ms3 {char A; short b; double c;} ms3; I think you must have the following correct layout: Padding | _____v________________________________ | | / | | / | | | | | / | | / | | --------------------------------- bytes: 1 1 2 4 8 Sizeof (Short) is equal to 2, B field Starting from the even address, then a byte is filled, and the sizeOf (double) is equal to 8, and the C field begins with 8x addresses. The front A, B field plus the padding byte already 4 Bytes, so B Then fill 4 bytes to ensure the alignment of the C field. SizeOf (MS3) is equal to 16, B offset is 2, and the offset is 8. Then look at the fields of the structure or the type of structure: Typedef struct ms4 {char A; ms3 b;} MS4; MS3 memory requirements The most stringent field is C, then the alignment of the MS3 type data is consistent with Double (8). After the A field, 7 bytes should be filled, so the layout of the MS4 should be: _______________________________________________________________________________________________________________________________________________________________________________________________________________________________ // | | | // padding // | b | | | /// | | --------------------------- ---------- BYTES: 1 7 16 Obviously, the offset equal to 24, B is equal to 8. In actual development, we can change the compiler alignment rules by specifying / zp compiling options. For example, specify /zpn (Vc7.1 can be 1, 2, 4, 8, 16) is to tell the compiler's maximum alignment mode number N. In this case, all alignment rules smaller than or equal to the n-byte, the alignment mode of the data type greater than the N-byte is limited to N. In fact, the default alignment option of VC7.1 is equivalent to / zp8. Take a closer look at the description of this option, it will find it solemnly warned the programmer not to use / zp1 and / zp2 options on the MIPS and Alpha platforms, do not specify / zp4 and / zp8 on the 16-bit platform (think why why ?). Changing the compiler alignment option, the controller running result Re-analysis The memory layout of the above four structures will be a good review. Here, we can answer the last question raised in this article. The memory layout of the structure depends on the CPU, operating system, compiler, and compile time alignment option, and your program may need to run on a variety of platforms, your source code may be compiled with different compiler. (Imagine that you provide an open source library for others), so unless it is absolutely necessary, your program will never rely on these weird memory layouts. By the way, if two modules in a program are compiled separately with different alignment options, it is likely to produce some very subtle errors. If your program does have a difficult behavior, you don't prevent carefully check the compilation options for each module. Thinking: Please analyze the memory layout of the following structures on your platform and try to find a reasonable arrangement of the sequence of field declarations to save memory space. A. Struct P1 {Int a; char b; int C; char d;}; b. struct p2 {int a; char b; char C; int D;}; C. Struct P3 {Short a [3]; char B [3];}; D. Struct P4 {short a [3]; char * b [3];}; E. Struct p5 {struct p2 * a; char b; struct p1 a [2];}; reference data: [1] "In-depth understanding of computer system (revised edition)", (and) Randal E.Bryant; David O'HALLARON, (ZD) Gong Yuli Yingchun, China Electric Press, 2004 [2] "C: a Reference Manual" (Copying Edition), (and) Samuel P.harbison; Guy L.Stele, People's Posts Publishing House, 2003