5. Structures, unions (memory alignment) and bit segments

Structure

Please also watch the blog related to the structure:

4. The keyword typedef is used for structure type redefinition

Structure declaration format

The declaration format of the structure:

struct name{
        ...
};
类型重定义后:
typedef struct name{
    ...
}name;

The struct name is the type name of the structure. Multiple data members can be defined in the middle of a pair of curly braces. If the structure weight is defined, the name is the structure type name, so as to simplify the type name and make it easy to use.


Structure self-referencing

The following is about the self-reference of the structure, for example:

typedef struct stu {
    char name[10];
    char number[18];
    struct stu st;
}stu;

The above code is wrong. The error code E0070 in VS indicates that incomplete types are not allowed. The correct structure self-reference should be a member variable is a pointer, as follows

typedef struct stu {
    char name[10];
    char number[18];
    struct stu* st;
}stu;


Structure size calculation

The size of the structure is not simply a rough addition to the size of the member variable, you need to consider the problem of memory alignment

First find out what memory alignment is

The memory space in modern computers is divided according to bytes. In theory, it seems that access to any type of variable can start from any address, but the actual situation is that when accessing a specific variable, it is often accessed at a specific memory address. This All types of data need to be arranged in space according to certain rules, instead of being arranged one after another in sequence, which is alignment.

The main role of memory alignment

Platform reason (transplant reason): Not all hardware platforms can access any data on any memory address; some hardware platforms can only fetch certain types of data at certain addresses, otherwise a hardware exception will be thrown.

Performance reasons: After memory alignment, the memory access speed of the CPU is greatly improved. The specific reason will be explained later.

Now let’s take a closer look at the alignment rules

The data members are aligned individually and the structure (or union) itself must also be aligned

Data members are aligned individually

For each member of the structure, the first member is located at an offset of 0, and the offset of each subsequent data member must be

multiple of min (#pragma pack(), length of itself)

Simply put, each compiler has its own default memory alignment number 1 , and the alignment number of each structure data member is the minimum value of the platform default alignment number and its own size.

Simply give a chestnut:

struct st1
{
    
    
	char a;
	int  b;
	short c;
};

struct st2
{
    
    
	short c;
	char  a;
	int   b;
};
#pragma pack() //vs编译器改回默认对齐数8
#include <stdio.h>
int main() {
    
    
	printf("%d\n", sizeof(struct st1));
	printf("%d\n", sizeof(struct st2));
	return 0;
}

The above code test platform is VS2019, and it is known that the alignment number is the default 8Bytes. Using the alignment rules of structure data members, you can know that the structure is laid out in memory as shown in the figure below:

Insert picture description here

According to the alignment rules of structure data members inside the structure, struct st1 is currently 10 and struct st2 is 8, but the actual size of the structure has not been calculated yet, and the alignment of the structure itself needs to be calculated.

The structure (or union) itself must also be aligned

After the data members have completed their respective alignments, the structure (or union) itself must also be aligned. The alignment will be aligned according to the value specified by ***#pragma pack and the minimum value of the maximum data member length of the structure (or union). ***

According to the rules, struct st1 is aligned to 4, struct st2 is aligned to 4, so the size of sizeof(struct st1) is finally 12, and the size of sizeof(struct st2) is 8.

Insert picture description here


Small test to improve the calculation of the size of the structure

Still the above examples of struct st1 and struct st2, we reduce the compiler's default alignment number for calculation

Change the compiler default alignment number to 4
    #include <stdio.h>
    #pragma pack(4)
    struct st1
    {
    
    
    	char a;
    	int  b;
    	short c;
    };
    struct st2
    {
    
    
    	short c;
    	char  a;
    	int   b;
    };
    #pragma pack()
    int main() {
    
    
    	printf("%d\n", sizeof(struct st1));
    	printf("%d\n", sizeof(struct st2));
    	return 0;
    }

#pragma pack(4) Set the compiler default alignment number to 4
#pragma pack() Set the compiler default alignment number back to the default value, vs default 8, VC6 default 8-byte alignment

The result is 12, 8

Change the compiler default alignment number to 2

The result is 8, 8

Change the compiler default alignment number to 1

The result is 7, 7

Memory space alignment after structure nesting

Give a chestnut:

#include <stdio.h>
#pragma pack()
struct s1 {
    
    
	double c1;
	int i;
	char c2;
};
struct s2 {
    
    
	char c1;
	struct s1 stmp;
	double d;
};
int main() {
    
    
	printf("%d\n", sizeof(struct s1));
	printf("%d\n", sizeof(struct s2));
	return 0;
}

As with the normal structure alignment rules, the data members are aligned first, and then the size of the structure is determined. The final result of the above example is: 16, 32



Combinations and positions

Consortium

Union is also a special custom type. Variables defined by this type also contain a series of members. The characteristic is that these members share the same space (so union is also called a union).

Type declaration for union type

union Un
{
 ...
};

Union Un is the name of the union type, ... is multiple data members

Definition of joint variables

union Un u; 

u is a union variable

Let's look at an example:

union Un
{
    
    
	int i;
	char c;
};
#include <stdio.h>
int main() {
    
    
	union Un un;
	// 下面输出的结果是一样的吗?
	printf("%d\n", &(un.i));
	printf("%d\n", &(un.c));
	//下面输出的结果是什么?
	un.i = 0x11223344;
	un.c = 0x55;
	printf("%x\n", un.i);
	return 0;
}

After the above code is executed, it is found that the starting addresses of the data members i and c are the same. The specific situation of the un union in the memory is as follows:

Insert picture description here

Calculation of joint size

When the maximum member size is not an integral multiple of the maximum alignment, it must be aligned to an integral multiple of the maximum alignment.

Give a chestnut:

#include <stdio.h>
union Un1
{
    
    
	char c[5];
	int i;
};
union Un2
{
    
    
	short c[7];
	int i;
};
int main() {
    
    
	//下面输出的结果是什么?
	union Un1 un1;
	union Un2 un2;
	printf("%d\n", sizeof(un1));
	printf("%d\n", sizeof(un2));
	return 0;
}

The specific situation of un1 and un2 in memory is as follows

Insert picture description here

The above result is 8, 16



Bit segment

First find out what the rank is

Position segment and structure are very similar in form and usage. But bit segments can be used to describe more detailed data levels. The structure can contain a variety of different data types, or even another structure type, but the smallest unit it can represent is a byte, that is, 8 bits. When programming a single-chip microcomputer, in many cases, it does not need so many bits to describe a value or a state, and maybe 3 or 4 bits are enough. For example, to describe "success" or "failure", only one bit is required. Then if 1 byte is used, 7 bits will be wasted. For example, in data communication, there are often certain bits in a byte that indicate a certain meaning. A typical application is that a certain bit is "1", which means that the data is valid, and vice versa. We need a structure at the "bit" level to describe it. This "bit" structure is defined as "bit segment".

There is a more convenient BitMap data structure in C++

What are the uses of bit segments

Bit segments are very convenient when dealing with bits. Let’s look at a scenario:

In the given long variable dat, dat we give the initial value 0xccccaccc, and extract the 13th to 15th bits, as shown in the following figure:

Insert picture description here

Use shifting method

First of all, we need to mask the bits other than 13~15

dat &= 0x0000e000 2

Then shift 13 bits to the right

that >> = 13

The theory is established, direct practice. Let's take a look at the code implementation:

#include <stdio.h>
int main() {
    
    
	long dat = 0xcccccccc; 
    dat = 0xccccaccc; //13~15位我们给101(十进制:5),于是dat=0xccccaccc
	//先屏蔽13~15位之外的位
	dat &= 0x0000e000;
	//然后右移13位
	dat >>= 13;
	printf("%d", dat);
	return 0;
}

The conclusion is correct

If we use the bit segment data structure at this time, we can easily control the value of the long type dat and quickly take out the 13~15 bit value
struct dat{
    unsigned int pre:13;
    unsigned int data:3;
    unsigned int nex:16;
}

Using the bit segment data structure can still artificially control the initial value of a long and modify the value

    #include <stdio.h>
    struct dat {
    
    
    	unsigned int pre : 13;
    	unsigned int d : 3;
    	unsigned int nex : 16;
    };
    int main() {
    
    
    	struct dat da; //给long的初始值为:0xccccaccc
    	//0xccccaccc二进制是:1100 1100 1100 1100 1010 1100 1100 1100,因此控制long的值如下:
    	
    	da.pre = 0xCCC; //0 1100 1100 1100
    	da.d = 5; //101
    	da.nex = 0xCCCC; //1100 1100 1100 1100
    	
    	printf("%x\n", *(unsigned int*)(&da)); //强制打印da的值,确定long的初始值是0xccccaccc
    	printf("%d\n", da.d); //取出13~15位的值
    	return 0;
    }

Inspired by printing the value of dat as long, the dat assignment process of the long type can be simplified as follows:

*(unsigned int*)(&da) = 0xccccaccc; 
#include <stdio.h>
struct dat {
    
    
	unsigned int pre : 13;
	unsigned int d : 3;
	unsigned int nex : 16;
};
int main() {
    
    
	struct dat da; //给long的初始值为:0xccccaccc
	*(unsigned int*)(&da) = 0xccccaccc; //控制long的值
	printf("0x%x\n", *(unsigned int*)(&da)); //强制打印da的值,确定long的初始值是0xccccaccc
	printf("%d\n", da.d); //取出13~15位的值
	return 0;
}

Bit segment memory alignment and how to calculate the size

The rules of bit segment memory allocation:

1. The bit segment will allocate a memory block of the size of the bit segment every time

2. Bits are allocated from right to left each time

3. If you can't finish it, open up a new space

  1. Unnamed bit field: 0 will force the memory of the next bit field to be aligned; a:1 indicates the size of 1 bit occupied by a; b indicates the memory occupied by one byte by default

    struct s {
    char a : 1;
    char : 0;
    char b;
    };

There is no digit after char b in struct s, the default is a char

    #include <stdio.h>
    struct s {
    
    
    	char a : 1;
    	char : 0;
    	char b;
    };
    int main() {
    
    
    	struct s S;
    	printf("%d\n", sizeof(S));
    	printf("%d\n", sizeof(S.b));
    	return 0;
    }

After memory alignment occurs, the memory space layout of struct s S is as follows:

Insert picture description here

To calculate the bit segment size, you need to comply with the above four rules at the same time. Let's look at the following sample questions:

Calculating the size of the bit segment for a small test

Look at the first example

    #include <stdio.h>
    struct A
    {
    
    
    	int _a : 2;
    	int _b : 5;
    	int _c : 10;
    	int _d : 30;
    };
    int main() {
    
    
    	printf("%d\n", sizeof(struct A));
    	return 0;
    }

The result is: 8

Set up the code for debugging:

#include <stdio.h>
struct A
{
    
    
    int _a : 2;
    int _b : 5;
    int _c : 10;
    int _d : 30;
};
int main() {
    
    
	struct A a;
	*(long long*)(&a) = 0xFFFFFFFFFFFFFFFF;
	a._a = 0;
	a._b = 0;
	a._c = 0;
	a._d = 0;
    printf("%d\n", sizeof(struct A));
    return 0;
}

Then debug the above code to know the memory layout of struct A a:

Insert picture description here

#include <stdio.h>
struct A
{
    
    
	int _a : 2;
	int _b : 5;
	int :0; //无名位域给0,下个位域会强制内存对齐
	int _c : 20;
	int _d : 30;
};
int main() {
    
    
	printf("%d\n", sizeof(struct A));
	return 0;
}

The result is: 12

Set up the code for debugging:

#include <stdio.h>
struct A
{
    
    
	int _a : 2;
	int _b : 5;
	int : 0; //无名位域给0,下个位域会强制内存对齐
	int _c : 20;
	int _d : 30;
};
int main() {
    
    
	struct A a;
	*(long long*)(&a) = 0xFFFFFFFFFFFFFFFF;
	*((int*)&a + 2) = 0xFFFFFFFF;
    a._a = 0;
	a._b = 0;
	a._c = 0;
	a._d = 0;
    printf("%d\n", sizeof(struct A));
    return 0;
}

Through the above code to debug, we can understand the memory layout of struct A a as:

Insert picture description here


  1. Unit byte ↩︎

  2. e0 is 1110 0000, which is guaranteed to leave 13~15 digits ↩︎

Guess you like

Origin blog.csdn.net/qq_43808700/article/details/113051610