📜 ⬆️ ⬇️

A few words about the size of structures in C / C ++ and why this happened

Below in the text, the term “platform” will call any given set of processor, compiler and operating system under which the compiled code will run.

Historically, the C language was created such that among the main objectives underlying it are:
A little bit about the variety of platforms. There are a huge number of them (platforms) - there are 16-bit, 32-bit and 64-bit among the processors. There are those who are able to perform floating-point operations at the hardware level, some support operations with double precision, and in some processors the FPU is completely absent. Processors also differ in the internal order of the bytes in the word (big / little endian), how exactly the processor works with external memory, etc. etc.

And for this whole zoo there is one and only Standard of the C language. How was it possible? This is where the fun begins.
')

The C language has very few restrictions on what its base types should be (char, short, int, long, float, double). Specifies the minimum number of bits for some types (for example, the type “char” must be at least 8 bits, “int” / “short” - 16, “long” - 32), it is specified that the sizes of the basic types are multiples of the size of the char, and the size of the char itself is identically equal to one.

And, for example: in MS-DOS programs, the size of the int and short is the same, and the types themselves were 16-bit. For the Win32 platform, the size of the int has already become different - 32 bits.

A bit of exotic: there are platforms (from Texas Instruments, for example), where the size of the basic types is very different from the “usual”. For example, the size of all base types is the same and equal to one. Those. sizeof (int) == sizeof (char) == sizeof (double). Yes, that's right - both char and short on this platform are 32-bit. The standard is not violated. Or such a platform: the “long” type occupies 8 bytes in memory, but only 5 are actually used there. sizeof (long) == 8, and ULONG_MAX = 1099511627775 or 2 40 -1. All this does not contradict the Standard, but at first it surprises.

Another important moment from the “iron world”, which had to be taken into account when developing the Standard, is how the processor works with memory. If you don’t go into how the address bus, data bus and memory chips function, then for the overwhelming number of architectures there is the following rule: if we read / write an N-byte value in one command, then its address must be a multiple N. That is, if 4-byte int is written to the memory, the address should be completely divided by 4. Similarly for 2-byte short'ov, etc.

What happens if this rule is not executed? On different platforms - in different ways: on some (ARMs, for example), the processor is interrupted and control is transferred to the OS kernel, on others (DSP from TI), the processor will silently write to the nearest multiple address (i.e., not where they said, and next ), and on the x86 platform, the processor ( for some types of data ) will do as the programmer implies, due to some drop in performance. It is important here to understand one thing - all processors work in the same way, when the address is equalized, and who is in that much - if this requirement is not met. That is why the definition of alignment (alignment) is defined at the very beginning of the Standard and is constantly mentioned when describing the language memory model.

What does all this lead from the programmer's point of view? This is best shown by example. Suppose we have such a structure:
struct Foo {
int iiii;
char c;
};


From the point of view of the programmer (inexperienced), the size of this structure is equal to sizeof (int) + sizeof (char) = 4 + 1 = 5 (we assume that the size of an int is 4 bytes). However, what happens if you declare an array of several elements of this type? In memory, they will be located as follows:
image

Or in words: the field iiii of the first element is located at the leveled address, and for the second element this is no longer true. Those. at the size of the structure Foo equal to 5, it is impossible to fulfill the requirements of memory model C.

For the same, so that the sheep were intact, and the wolves are fed, the compiler inserts "invisible" (for the programmer) additional 3 bytes at the end of the structure (the so-called padding bytes). This leads to the fact that the size of the structure becomes equal to 8 and in memory the array begins to be located as follows:
image

However, this is not all that the compiler does with this structure. In addition to the fact that the size of Foo is 8, the compiler also remembers that the minimum alignment requirement for this entire structure is 4 bytes. The differences are easy to show with the following example:
struct Foo {
int iiii;
char c;
};

struct Bar {
char c8[8];
};

struct Test1 {
char c;
Foo foo;
};

struct Test2 {
char c;
Bar bar;
};

Here such moments are interesting: the size and Foo and Bar are the same and equal to 8 bytes. But I align requirements for Foo - 4 bytes, and Bar - 1. This leads to the fact that in the structure of Test1, between 'c' and 'foo', the compiler inserts an additional 3 bytes to ensure that the field 'foo' will always be Begin at the address multiple of 4th. In the structure of Test2, you do not need to do anything of this kind, as a result, sizeof (Test1) is 12, and sizeof (Test2) is 9. That is, we got a different result by combining the “bricks” of the same size!

If this is all interesting, then the topic can be continued.

Source: https://habr.com/ru/post/90580/


All Articles