Hand tearing custom types: structure, enumeration, union - [C language]

 Before starting to study, let's enjoy the scenery of Huashan Mountain, one of the five mountains, to create a good mood. Only with a good mood can we study better

Table of contents

structure

1 Declaration of structure

1.1 Basic knowledge of structure

1.2 Declaration of structure

1.3 Special notices

1.4 Self-referencing of structures

1.5 Definition and initialization of structure variables 

1.6 Structure memory alignment (emphasis)

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 for bit segments

2.3 Cross-platform issues with bit segments

3. Enumeration

3.1 Definition of enumerated types

3.2 Advantages of enumeration

 3.3 Use of enumeration

 4. Union (community)

4. Union (community)

4.2 Characteristics of the union

4.3 Calculation of joint size   


structure

1 Declaration of 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 a different type.

1.2 Declaration of structure

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

For example to describe a student:

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

1.3 Special notices

When declaring a structure, it can be incomplete. 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 declaring it. So here comes the question?

//Based on the above code, is the following code legal?

p = &x;

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

1.4 Self-referencing of structures

Is it okay to have a member in a struct that is of type the struct itself?

//代码1
struct Node
{
 int data;
 struct Node next;
};
//可行否?
如果可以,那sizeof(struct Node)是多少?

The answer is not feasible, because self-references in structure variables will cause infinite nesting dolls. When asking for the size of struct Node, it includes itself, but its own size is unknown, so this way of writing is wrong!

The correct way to write it is:

//代码2
struct Node
{
 int data;
 struct Node* next;
};

Here's another question:

Is it okay to self-reference pointers when renaming with typedefs? code show as below:

typedef struct
{
 int data;
 Node* next;
}Node;
//这样写代码,可行否?

The answer is no, because the execution order of the code is from top to bottom, and the typedef renaming is given a new name at the end. It is wrong to use it in the structure! The specific solution is as follows:

typedef struct Node
{
 int data;
 struct Node* next;
}Node;

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};//结构体嵌套初始化(第五种)

All the above five methods have been shown clearly in the form of code. 

1.6 Structure memory alignment (emphasis)

We have mastered the basic use of structures. Now we dive into a problem: calculating the size of a structure.

This is also a particularly popular test point: structure memory alignment! ! !

Let's start with a program:

struct S1
{
	char c1;
	int i;
	char c2;
};
int main(void)
{
	printf("%d\n", sizeof(struct S1));
	return 0;
}

What should be the size of struct S1? We generally think it is 6 at the beginning

 Then why is the result 12? We first pass a macro offsetof (calculate the offset of the structure member compared to the start position of the structure). This macro is in the header file #include<stddef.h>.

#include<stddef.h>
struct S1
{
	char c1;
	int i;
	char c2;
};
int main(void)
{
	//printf("%d\n", sizeof(struct S1));
	printf("%d\n", offsetof(struct S1, c1));
	printf("%d\n", offsetof(struct S1, i)); 
	printf("%d\n", offsetof(struct S1, c2));
	return 0;
}

  The content of the structure is full, why continue to waste these three bytes?

Let's learn:

How to calculate?

First, you must master the alignment rules of the structure:

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

2. Other member variables should be aligned to an address that is an integer multiple of a certain number (alignment number). Alignment = Compiler's default alignment and the smaller value of the member size. The default value in VS is 8. There is no default alignment number in Linux, and the alignment number is the size of the member itself

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

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

After learning the above memory alignment rules, we should understand all the questions above, so now we are practicing a question:


#include<stddef.h>
struct S1
{
	char c1;
	char c2;
	int i;
};
int main(void)
{
	printf("%d\n", sizeof(struct S1));
	return 0;
}

Let's make a concrete analysis of the above structure:  Is the result 8? Let's verify it:  Yes, I believe that everyone has basically understood and mastered the memory alignment of the structure. So why do we need memory alignment?

reason:

1. Platform reasons (reasons for porting): Not all hardware platforms can access any data at any address; some hardware platforms can only fetch certain types of data at certain addresses, 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 to access unaligned memory, the processor needs to make two memory accesses; while aligned memory accesses require only one access.

In general: The memory alignment of structures is a practice of exchanging space for time.

 When designing the structure, we must satisfy the alignment and save space. How to do it:

Let members who occupy a small space gather together as much as possible.

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

The members of the S1 and S2 types 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 

We have seen the #pragma preprocessing directive before, and here we use it again to change our default alignment

#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 we set the alignment to 1, the memory size of the same structure changes from 12 to 6.

1.8 Structure parameter passing

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 above print1 and print2 functions is better?

The answer is: the print2 function is preferred. reason:

When a 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, the structure is too large, and the system overhead of pushing the parameters on the stack is relatively large, which will lead to a decrease in performance.

 2. Bit segment

2.1 What is a bit segment

Bit field declarations and structures are similar, with two differences:

1. The members of the bit field must be int, unsigned int or signed int.

2. There is a colon and a number after the member name of the bit segment.

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

A is a bit segment type. What is the size of segment A? 

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

 With the questions just now, let's look down.

2.2 Memory allocation for bit segments

1. The members of the bit segment can be int unsigned int signed int or char (belonging to the integer family) type

2. The space of the bit field is opened up in the form of 4 bytes (int) or 1 byte (char) according to the need.

3. Bit segments involve many uncertain factors. Bit segments are not cross-platform. Programs that focus on portability should avoid using bit segments.

for example: 

//一个例子
struct S
{
 char a:3;
 char b:4;
 char c:5;
 char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
//空间是如何开辟的?

Next let's analyze:

 We can see at a glance the memory allocation of the bit segment in vs.

2.3 Cross-platform issues with bit segments

1. Whether an int bit field is treated as a signed number or an unsigned number is undefined.

2. The maximum number of bits in a bit field cannot be determined. (16-bit machines can be up to 16, 32-bit machines can be up to 32, written as 27, there will be problems on 16-bit machines.

3. Whether the members in the bit segment are allocated from left to right in memory or from right to left has not yet been defined.

4. When a structure contains two bit segments, and the second bit segment is too large to accommodate the remaining bits of the first bit segment, it is uncertain whether to discard or utilize the remaining bits.

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

3. Enumeration

Enumeration, as the name suggests, is to enumerate one by one. List all possible values. For example, in our real life: Gender: male, female, confidential, you can also list them one by one. There are 12 months in the month, and they can also be listed one by one.

3.1 Definition of enumerated 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 content in {} is the possible value of the enumeration type, also called enumeration constant.

These possible values ​​are all valid, starting from 0 by default and increasing by 1 in turn. Of course, the initial value can also be assigned when declaring the enumeration type.

 For example:

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

3.2 Advantages of enumeration

We can use #define to define constants, why use enums?

The advantages of enumeration: 1. Increase the readability and maintainability of the code 2. Compared with the identifier defined by #define, enumeration has type checking, which is more rigorous. 3. Easy to debug 4. Easy to use, multiple constants can be defined at a time 

 3.3 Use of enumeration

enum Color//颜色
{
 RED=1,
 GREEN=2,
 BLUE=4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
clr = 5;               //ok??

 4. Union (community)

4. Union (community)

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

union Un
{
	char c;
	int i;
};

int main()
{
	union Un un = { 0 };
	printf("%d\n", sizeof(un));
	printf("%p\n", &un);
	printf("%p\n", &(un.i));
	printf("%p\n", &(un.c));

	return 0;
}

No matter which part of the union is accessed, the address is the same, which means that the variables in the union share the same memory space, and no one will be allocated for each variable, and the memory size is 4, which is the largest in the union .

Therefore, only one element in the union can be used at the same time, otherwise it will interfere.

We can use a piece of code to prove it:

union Un
{
	char c;
	int i;
};
int main()
{
	union Un un = { 0 };
	un.i = 0x11223344;
	un.c = 0x55;

	return 0;
}

4.2 Characteristics of the union

The members of the union share the same memory space, so the size of such a joint variable is at least the size of the largest member (because the union must at least have the ability to save the largest member). 

4.3 Calculation of joint size   

The size of the union is at least the size of the largest member.

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

Let's practice: 

union Un1
{
 char c[5];
 int i;
};
//下面输出的结果是什么?
printf("%d\n", sizeof(union Un1));

 What was the result?


That's all for now, thanks for watching! ! !

Guess you like

Origin blog.csdn.net/m0_74755811/article/details/131757185