C language--custom types: structures, enumerations, unions (1)

Table of contents

1 structure

1.1 Basic knowledge of structure

1.2 Declaration of structure

 1.3 Special Statement

1.4 Self-reference of structures

1.5 Definition and initialization of structure variables

1.6 Structure memory alignment

1.6.1 Structure alignment rules

1.6.2 Why structures need to be aligned

1.7 Modify the default alignment number

1.8 Structure parameter passing

2. bit segment

2.1 What is a bit segment?

2.2 Memory allocation of bit segments

2.3 Cross-platform issues in bit segments

  3. Enumeration

3.1 Definition of enumeration types

3.2 Advantages of enumerations

3.3 Use of enumerations

4. Union (community)

4.1 Definition of union types

4.2 Characteristics of the consortium

4.3 Calculation of union size


1 structure

1.1 Basic knowledge of structure

A structure is a collection of values ​​called member variables. Each member of the structure can be a variable of different types.


1.2 Declaration of structure

struct tag
{
member-list;
}variable-list;

For example, describe a student:

struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //分号不能丢

 1.3 Special Statement

can be incompletely declared when declaring a structure.
For example:

//匿名结构体类型
struct
{
    int a;
    char b;
    float c;
}x;
struct
{
    int a;
    char b;
    float c;
}a[20], *p;

       The above two structures omit the structure tag (tag) when they are declared.
So here’s the question? Based on the above code, is the following code legal?

p = &x;

Warning:
        The compiler will treat the two declarations above as two completely different types.
So it is illegal.


1.4 Self-reference of structures

        When the type of a member variable in a structure is the structure itself, we call it a self-reference of the structure. This self-referential structure is very common in the C language, which allows us to create linked lists, trees, graphs and other data structures containing structures of the same type.

        So the question is, how do we implement a linked list? We know that the sequence list only needs to know the starting position to find all the subsequent data. A single linked list does not work because the linked list is not stored continuously in the memory. We need to know the address of the next node to get the address of the next node. . Then we can just save the address of the next node after the current data, so that the data can be linked into a string.


Is it possible to include a member of the structure itself?

//代码1
struct Node
{
    int data;
    struct Node next;
};

        We store the current data in the structure, which contains the next structure of the same type. Can we concatenate the data in this way? Assuming that this method is feasible, what is sizeof(struct Node)?

        ​​​​Obviously, the C language does not support this writing method, because the member next is another structure, and the type is struct Node, which also contains its own member next. This repeats endlessly, like a recursive program that never ends. So this approach won't work.


Is it feasible if contains a pointer to the type of the structure itself in the structure? ? As shown in the picture:


 

        ​​​​Obviously this way of writing is correct, because the second member variable of the structure type designed in this way is essentially a pointer, and the pointer is nothing more than 4/8 bytes, so it is impossible to be unable to calculate.


1.5 Definition and initialization of structure variables

With the structure type, how to define variables is actually very simple.

struct Point
{
	int x;
	int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = { x, y };
struct Stu //类型声明
{
	char name[15];//名字
	int age; //年龄
};
struct Stu s = { "zhangsan", 20 };//初始化
struct Node
{
	int data;
	struct Point p;
	struct Node* next;
}n1 = { 10, {4,5}, NULL }; //结构体嵌套初始化
struct Node n2 = { 20, {5, 6}, NULL };//结构体嵌套初始化

1.6 Structure memory alignment

        In C language, structure memory alignment is a mechanism to optimize memory access efficiency. When a structure is defined, the compiler determines how the structure members are arranged in memory according to certain rules. Next let's look at a set of examples:

struct S1
{
	char a;
	int b;
	char c;
};
struct S2
{
	char a;
	char b;
	int c;
};
int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

        The two structures have the same type, and the members are composed of two char types and one int type, but the ordering is different. The results should be the same, (1+1+4=6), but the actual results are not like this. .

        At this time, someone will say that 6 bytes are enough. Why does this happen? The sorting is different, and the memory size occupied also changes accordingly. Next, let’s talk about the knowledge of structure memory alignment. How to align it? Why align?


1.6.1 Structure alignment rules

 1. The first member is at the address offset 0 from the structure variable.

(The position of the first member of the structure must be the same as the starting position of the structure)

as the picture shows:

2. Other member variables should be aligned to an address that is an integer multiple of a certain number (alignment number).
Alignment number = The compiler default alignment number The smaller of the size of the member.
The default value in VS is 8

3. The total size of the structure is an integer multiple of the maximum alignment number (each member variable has an alignment number).

The arrangement of structure s1 happens to be that 8 is an integer multiple of the maximum alignment number i (4), so the final result is right.

If the size is 9 according to the arrangement of s2, it is not an integer multiple of the maximum alignment number i (4), so it needs to be supplemented to 12.

4. If a structure is nested and the nested structure is aligned to an integer multiple of its own maximum alignment number, the overall size of the structure is an integer of all maximum alignment numbers (including the alignment number of nested structures) times

We calculate the size of s3 to be 16. In s4, c1 starts from 0, and in s3 (the nested structure is aligned to an integer multiple of its own maximum alignment number), the maximum alignment number in the s3 structure is (double d) 8, so the calculation starts from an integer multiple of 8, and finally Add the double d size in s4 to 32, (the overall size of the structure is an integer multiple of all the maximum alignment numbers (including the alignment number of nested structures)) = 8, 32 is an integer multiple of the maximum alignment number 8, so the structure The body size is 32.


1.6.2 Why structures need to be aligned

        Structure alignment will lead to a waste of space, so why do we need to align it?

1. Platform reasons (transplantation reasons):
        Not all hardware platforms can access any data at any address; some hardware platforms can only access any data at certain addresses. Get certain types of data, otherwise a hardware exception is thrown.

2. Performance reasons:
        Data structures (especially stacks) should be aligned on natural boundaries as much as possible. The reason is that in order to access unaligned memory, the processor needs to make two memory accesses; aligned memory access requires only one access.

Generally speaking: memory alignment of structures is a way of exchanging space for time.

When designing the structure, we must not only satisfy the alignment, but also save space. How to do it:
       Let the members that occupy small space be gathered together as much as possible.

//例如:
struct S1
{
    char c1;
    int i;
    char c2;
};
struct S2
{
    char c1;
    char c2;
    int i;
};

The members of type S1 and S2 are exactly the same, but there are some differences in the size of the space occupied by S1 and S2.


1.7 Modify the default alignment number

        Of course, the default alignment number can also be modified. We have seen the #pragma preprocessing directive before, and here we use it again to change our default alignment number. (Generally set to the power of 2)

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
	//输出的结果是什么?
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

 When the alignment of the structure is inappropriate, we can change the default alignment number ourselves.

1.8 Structure parameter passing

Go directly to the code:

struct S
{
	int data[1000];
	int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{
	printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
	printf("%d\n", ps->num);
}
int main()
{
	print1(s); //传结构体
	print2(&s); //传地址
	return 0;
}

Which of the print1 and print2 functions above is better?
The answer is: the print2 function is preferred.
Reason: When the function passes parameters, the parameters need to be pushed onto the stack, which will cause system overhead in time and space. If a structure object is passed and the structure is too large, the system overhead of pushing parameters onto the stack will be relatively large, which will lead to performance degradation.
Conclusion: When passing parameters to a structure, the address of the structure must be passed.

2. bit segment

        After talking about the structure, we have to talk about the ability of the structure to realize the bit segment . (Bit segments appear to save space.)

2.1 What is a bit segment?

The declaration and structure of the bit field are similar, with two differences:
        1. The members of the bit field must be int, unsigned int or signed int. (It can also be other types after c99, but it is basically int, char.)
        2. There is a colon and a number after the member name of the bit field.

for example:

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

The bits in the bit field are binary bits. For example, _a occupies 2 bits. If _a is only used to store single digits, then the size of _a is just enough, thus saving space.

Let’s take a look at a group comparison:

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
struct B
{
	int a ;
	int b ;
	int c ;
	int d ;
};

int main()
{
	printf("%d\n", sizeof(struct A));
	printf("%d\n", sizeof(struct B));
	return 0;
}

 

 Obviously bit segments save a lot of space, but when using bit segments, you need to know the size of the data at each location.


2.2 Memory allocation of bit segments

1. The members of the bit field can be int unsigned int signed int or char (belonging to the integer family) type
2. The space of the bit field is 4 words as needed It is opened in section (int) or 1 byte (char) format.
3. Bit segments involve many uncertain factors. Bit segments are not cross-platform. Programs that focus on portability should avoid using bit segments

Let's look at an example:

struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};

int main()
{
	struct S s = { 0 };

	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	printf("%d\n", sizeof(struct S));
	return 0;
}


2.3 Cross-platform issues in bit segments

1. It is undefined whether the int bit field is regarded as a signed number or an unsigned number.
2. The maximum number of bits in the bit field cannot be determined. (The maximum limit for a 16-bit machine is 16, and the maximum limit for a 32-bit machine is 32. Writing it as 27 will cause problems on a 16-bit machine.
3. Are the members in the bit segment allocated from left to right in the memory? The right-to-left allocation standard has not been defined.
4. When a structure contains two bit fields, the members of the second bit field are larger and cannot fit in the remaining bits of the first bit field. , it is uncertain whether to discard the remaining bits or use them

Summary: Compared with structure, bit segments can achieve the same effect, but can save space very well, but there are cross-platform problems.


  3. Enumeration

enumeration, as its name implies, is to enumerate them one by one.
List the possible values ​​one by one.
For example, in our real life: Monday to Sunday are a limited number of 7 days in a week, and they can be listed one by one.
Gender includes: male, female, confidential, you can also list them one by one.
There are 12 months in the month, you can also list them one by one

3.1 Definition of enumeration types

enum Day//星期
{
	Mon,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};
enum Sex//性别
{
	MALE,
	FEMALE,
	SECRET
};
enum Color//颜色
{
	RED,
	GREEN,
	BLUE
};

        The enum Day , enum Sex , and enum Color defined above are all enumeration types. The contents in {} are possible values ​​of the enumeration type, also called enumeration constants

Continue the above code:

int main()
{
	
	printf("%d\n", MALE);
	printf("%d\n", FEMALE);
	printf("%d\n", SECRET);
	return 0;
}

These possible values ​​are all valid, starting from 0 by default and increasing by 1 at a time. Of course, an initial value can also be assigned when defining.
For example:

enum Color//颜色
{
    RED=1,
    GREEN=2,
    BLUE=4
};

3.2 Advantages of enumerations

Why use enumerations?
We can use #define to define constants, why do we have to use enumerations?
Advantages of enumeration:
1. Increase the readability and maintainability of the code
2. Definition with #define Compared to enumerations, identifiers have type checking, which is more rigorous.
3. Prevents naming pollution (encapsulation)
4. Easy to debug
5. Easy to use, multiple definitions can be defined at one time constant


3.3 Use of enumerations

Example:

#include <stdio.h>

// 定义一个枚举类型Season,表示四个季节
enum Season {
    SPRING,
    SUMMER,
    AUTUMN,
    WINTER
};

int main() {
    enum Season currentSeason = SPRING;

    // 使用switch语句根据当前季节输出不同的信息
    switch (currentSeason) {
        case SPRING:
            printf("It's spring!\n");
            break;
        case SUMMER:
            printf("It's summer!\n");
            break;
        case AUTUMN:
            printf("It's autumn!\n");
            break;
        case WINTER:
            printf("It's winter!\n");
            break;
        default:
            printf("Invalid season!\n");
            break;
    }

    return 0;
}

In this example, we define an enumeration type Season, which contains four constants: SPRING, SUMMER, AUTUMN and WINTER, representing spring, summer, autumn and winter respectively.

In the main function, we declare a currentSeason variable and initialize it to SPRING . We then use a switch statement to output different information based on the current season.

When we run this code, the output will be: "It's spring!" because the value of currentSeason is SPRING .

Enum types make code clearer and more readable because we can use semantic constants to represent specific states or options. In addition, enumeration types can also be used to define the return value of a function, members of a structure, etc.


4. Union (community)


4.1 Definition of union types

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

union Un
{
	char c;
	int i;
};
int main()
{
	//联合变量的定义
	union Un un = {0};
	//计算连个变量的大小
	printf("%d\n", sizeof(un));
}

At this time we have a question, the result is not 5, why is it 4? Let's take a look at the address where the variable is stored.

printf("%p\n", &un);
printf("%p\n", &(un.c));
printf("%p\n", &(un.i));

        ​ ​ ​ We found that their addresses are all the same. This proves that the members of the union share the same space, so it is also called a union. Therefore both members cannot be used at the same time.


4.2 Characteristics of the consortium

The members of a union share the same memory space, so the size of a union variable must be at least the size of the largest member (because the union must be able to store at least the largest member).

We can use the union to determine the big and small endian storage of the current machine.

int check_sys()
{
	union
	{
		char c;
		int i;
	}u;

	u.i = 1;
	return u.c;//返回1表示小端,返回0表示大端
}

int main()
{
	int ret = check_sys();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");

	return 0;
}

        When we judge the machine's big-endian storage, we actually only need to judge the data stored in the first byte of the data storage. We defined i in the union, and c can just take out the first word of i. section data.


4.3 Calculation of union size

1. The size of the union is at least the size of the largest member.
2. When the maximum member size is not an integer multiple of the maximum alignment number, it must be aligned to an integer multiple of the maximum alignment number.

for example:

union Un1
{
	char c[5];
	int i;
};
int main()
{
	printf("%d\n", sizeof(union Un1));
}

 

Analysis: char[5] is actually 5 char types, the alignment number is 1, and the maximum alignment number is (int) 4, so it must be an integer multiple of 4, so the result is 8.


End of this section!​ 

Guess you like

Origin blog.csdn.net/2301_76618602/article/details/133200566