C language--detailed explanation of self-defined types

structure

The basic knowledge of the structure has already been mentioned in the previous blog. In this article, we will do some review to review the basic knowledge of the structure
.

declaration of the structure

The declaration of the structure is as follows

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

Suppose we want to use a structure to describe a student

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

special statement

There is a special way of struct declaration called anonymous struct
. For example

struct S//把S去掉就是匿名结构体
{
    
    
int a;
char b;
float c;
}x;

Anonymous structures are supported syntactically, but anonymous structures have a disadvantage, that is, they can only be added after struct, that is, they can only be global variables . This type can no longer be used elsewhere.

So can we create another anonymous structure pointer and put the address of this anonymous structure in it?

struct
{
    
    
int a;
char b;
float c;
}x;
struct 
{
    
    
int a;
char b;
float c;
}* ps;

int main
{
    
    
ps=&x;//?
}

The answer is no , this is because the compiler will treat the above two declarations as two completely different types.
So it's illegal
.

self-referencing structure

In fact, the self-reference of this structure is related to the linked list in the data structure. In addition to storing its own data, a node must also be able to find the next node . The first thing we think of is to put the next node directly into this node,
see the following code

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

At first glance, there is no problem, but in fact, this will not work, because each next will have the data of the next node, so it will go on indefinitely . Obviously there is a problem.

The correct way is to store the address of the next node. Because an address is either four or eight bytes, the size of such a node can be determined

//正确写法
struct Node
{
    
    
int data;
struct Node* next;
};

Use of typedefs

Let's look at these two pieces of code

struct 
{
    
    
int a;
char b;
float c;
} x;

typedef struct 
{
    
    
int a;
char b;
float c;
} x;

Notice! The meanings of these two x's are completely different. The first x is an anonymous struct variable , while the second x is a struct type

Let's take the node creation just now as an example,
first look at the following code

typedef struct
{
    
    
int data;
Node* next;
}Node;
//这样写可以吗?

The answer is no , because the structure has not been renamed to Node when inside the curly braces, so Node* cannot be used directly. It can be seen that it is not recommended to use an anonymous structure in this case. honestly write

typedef struct Node
{
    
    
int data;
struct Node* next;
}Node;//在这之后你想在对其重命名为Node就没问题了

Definition and initialization of structure variables

This knowledge has also been discussed in detail in the previous blog, here is just a brief review

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};//结构体嵌套初始化

memory alignment of structures

This knowledge point is very important! It must be understood and transparent!
Let's look at the following three pieces of code and think about the size of these three structures?

struct S1
{
    
    
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));
//练习2
struct S2
{
    
    
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));
//练习3
struct S3
{
    
    
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));
//练习4-结构体嵌套问题
struct S4
{
    
    
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct S4));

Taking the first piece of code as an example, our fixed thinking may think that it is enough to add the type size of each member variable of the structure, that is, 1(char)+4(int)+1(char)=6 . But is this really the case? Let's run it on the compiler to see.
Figure 1
figure 1

The answer is actually 12. Obviously, the compiler must not make mistakes, so we have to figure out what the principle is.
Let's look at the alignment rules:

  1. The first member is at offset 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, and there is no default alignment number in the gcc environment)
  3. The total size of the structure is an integer multiple of the maximum alignment (each member variable has an alignment) .
  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 an integer multiple of all maximum alignments (including the alignment of the nested structure) .

Let's take the first piece of code as an example to explain it.
Look at Figure 2.
figure 2
What if there is nesting? Looking at Figure 3
image 3
, we have explained the four alignment rules here.

Why does memory alignment exist?

  1. Platform reason (transplant reason):
    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 in order to access unaligned memory, the processor needs to make two memory accesses; while aligned memory access requires only one access.

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

Thinking: How can we achieve alignment while saving space as much as possible ?
Through the previous few examples, we found that the char type, which occupies a small space, is always a waste of space. Therefore, we want to gather members who occupy a small space together as much as possible . This can effectively reduce the waste of space.

Modify the default alignment

#pragma is a preprocessing directive that can modify our default alignment.

for example

#pragma pack(8)//设置默认对齐数为8
struct S1
{
    
    
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

Do not use odd multiples of alignment when modifying the default alignment , it is very unreasonable.

Struct parameter passing

Structure parameter passing, selection of call by value and call by address, and stack push operation are also explained in detail in previous blogs, so I won’t go into details here, just review it.

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;
}

bit segment

The declaration and structure of the bit field are similar, but there are 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 .

Look at the code below

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

What is the result of this code printing?
As shown in Figure 4,
Figure 4
we found that it is 8 bytes, which saves almost the same space as the original four ints (ie 16 bytes).
So how exactly does it carve out space? This is what we're going to explore next

bit segment memory allocation

The rules are as follows

  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 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 , and 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;

Figure 5

Cross-platform issues with bit segments

  1. It is undefined whether an int bit field is treated as signed or unsigned.
  2. The maximum number of bits in a bit field cannot be determined. (16-bit machines have a maximum of 16, 32-bit machines have a maximum of 32, written as 27, there
    will be problems on 16-bit machines.
  3. Whether members of a bit field are allocated from left to right in memory or from right to left is undefined.
  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.

In general: Compared with the structure, the bit field can achieve the same effect, but it can save space very well , but there are cross-platform problems .

enumerate

Listing out the possible values ​​​​one by one is called an enumeration.
In C language, we implement enumeration through the keyword enum

give a few examples

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

The Mon, Tues, MALE, and FEMALE are all numbers in essence.
Let’s take the week as an example and print the results as follows.
Figure 6
We found that the possible values ​​of the enumeration start from 0 and increase by 1 by default . We can also modify the default value by assignment.

Enumeration has obvious advantages in improving code readability . For example, in a switch statement, when we input different numbers such as 0, 1, 2, and 3, they correspond to different follow-up operations, but only some numbers cause code readability. Not so high, but if we change the corresponding value to a specific operation name , the readability of the code will be greatly improved. This effect is clearly reflected in the writing of the address book.

joint

union type definition

Union is also a special custom type.
The variable defined by this type also contains a series of members, which are characterized by the fact that these members share the same space (so union is also called union)

Unions open up space in memory

Let's first look at a piece of code

union Un
{
    
    
int i;
char c;
};
union Un un;
// 下面输出的结果是一样的吗?
printf("%d\n", &(un.i));
printf("%d\n", &(un.c));

Figure 7
It can be seen that the two variables i and c occupy the same space, and we can use drawing to help us understand.
Figure 8
We can use this feature of the union to help us judge the size of the machine.
The code is as follows

union Un
{
    
    
	int i;
	char c;
};
union Un un;
int main()
{
    
    
	union Un u;
	u.i = 1;
	if (u.c == 1)
	{
    
    
		printf("小端");
	}
	else
	{
    
    
	    printf("大端");
	}
}

Figure 9

Calculation of joint size

The union is also aligned,
see the following code

union Un1
{
    
    
	char c[5];
	int i;
};
union Un2
{
    
    
	short c[7];
	int i;
};

int main()
{
    
    
	printf("%d\n", sizeof(union Un1));
	printf("%d\n", sizeof(union Un2));
}

The print result is as shown in the figure.
Figure 10
When calculating the joint size, there are also the following rules
: 1. The size of the joint 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.

The above is the whole content of the detailed explanation of the custom type. If there is any discrepancy, please correct me.

Guess you like

Origin blog.csdn.net/m0_75233943/article/details/129687851