[Advanced C Language: Detailed Explanation of Custom Types] Structure

Highlights of this section:

  • Basic knowledge of structure
  • declaration of structure
  • special statement
  • self-referencing structure
  • Definition and initialization of structure variables
  • Structure memory alignment
  • Modify the default alignment
  • Struct parameter passing

The C language has built-in types char, short, int, long, float, double , and these types can be used directly by us. In addition to these types, the C language also allows us to create some types ourselves, which we call custom types , including structures, unions, enumeration types, and so on. Next I will describe these custom types in detail.


⚡ Basics 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 . When it comes to collections, you have to mention arrays, but unlike structures, arrays are a collection of elements of the same type .


⚡ Structural declarations

//结构体声明的基本方式
struct tag
{
    member-list; //成员列表
}variable-list; //变量列表

For example, to describe a student, the code example is as follows:

//定义学生类型
struct Stu
{
char name[20];    //名字
int age;          //年龄
float weight;     //体重
}s4,s5,s6;    //全局变量

int main()
{
    //利用定义的学生类型来创建变量
    struct Stu s1;   //局部变量
    struct Stu s2;
    struct Stu s3;
    return 0;
}

In this code, we can either create variables directly after defining the structure type (that is, the variable list column in the basic way of structure declaration), or create variables inside the main function, but there are differences between the two : The variables created directly after defining the structure are global variables (there may be none or multiple), while the variables created in the main function are local variables .


⚡Special statement

Special declaration means that when declaring a structure, it can be incompletely declared, that is, the name of the structure can be omitted. We refer to such struct types as anonymous struct types.

//匿名结构体类型
struct
{
    int a;
    char b;
    float c;
}s1;

For anonymous struct types, it is available, but the required variables must be created directly after the anonymous struct type is defined. But this also makes the variable of the anonymous structure type created can only be used once in the entire project, because there is no name of the structure type in the second use, so the variable cannot be successfully created.

Error-prone points of anonymous struct types:

struct 
{
    char c;
    int a;
    double d;
}s1;

struct 
{
    char c;
    int a;
    double d;
}*ps;

int main()
{
    ps = &s1;  //err
    return 0;
}

Error list: 

 So what is the reason for this error? It turns out that from the perspective of the compiler, although the structure type members of s1 and *ps are exactly the same, the compiler will regard the above two declarations as two completely different types . So don't use anonymous struct types like this.


⚡ Self-referencing of structures

When understanding the self-reference of the structure, the data structure is introduced here. The data structure essentially describes the data structure of the data in memory. To store a set of data (1, 2, 3, 4, 5) scattered in memory in a data structure, a linked list is required:

 In order to be able to find the next node through this node, the structure type we declare should contain both its own data and the information of the next node, so the structure type should be declared like this:

struct Node
{
    int data;
    struct Node next;
}; //err sizeof(struct Node)是多大呢?要开辟的内存又不可知,因此此写法错误

//正确的自引用方式:
struct Node
{
    int data;
    struct Node* next;
};
typedef struct
{
    int data;
    Node* next;
}Node; //err

//正确的自引用方式:
typedef struct Node
{
    int data;
    struct Node* next;
}Node;

⚡Definition and initialization of structure variables

With the structure type, how to define and initialize variables is very simple:

//结构体变量的定义

struct S
{
    int x;
    int y;
}s1; //声明类型的同时定义变量s1  ……  1

struct S s2;   //  ……  2

int main()
{
    struct S s3;    // ……  3
    return 0;
}
//结构体变量的初始化

struct S
{
    int a;
    char c;
};

struct B
{
    float f;
    struct S s;
};

int main()
{
    struct S s2 = {100,'q'};
    struct B sb = {3.14f,{200,'w'}};
    return 0;
}

Monitor content:

You can also use a compelling initialization method - specifying initializations not in order:

struct S
{
    int a;
    char c;
};

struct B
{
    float f;
    struct S s;
};

int main()
{
    struct S s2 = {100,'q'};
    struct S s3 = { .c = 'r', .a = 2000};
    struct B sb = {3.14f,{200,'w'}};
    return 0;
}

 Monitor content:


⚡Structure memory alignment

We can discuss one problem in depth: computing the size of a struct . This is also a particularly popular test point: structure memory alignment

struct S1
{
    int a;
    char c;
};

struct S2
{
    char c1;
    int i;
    char c2;
};

struct S3
{
   char c1;
   int a;
   char c2;
   char c3;
};

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

The result of running the code is as follows:

Alignment rules for structures:

  • The first member of the structure is always placed at offset 0.
  • Starting from the second member, other member variables must be aligned to integer multiples of a certain number (alignment number).
  • This alignment is: the smaller value of the member's own size and the default alignment. 
  • The default alignment number of VS is 8; there is no default alignment number in the gcc environment, and when there is no default alignment number, the alignment number is the size of the member itself.
  • When all the members are put in, the total size of the structure must be an integer multiple of the largest alignment among all members. If not enough, space is wasted for alignment.
  • 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).

Mind Diagram:

 Is that really the case? We can verify it through code:

  • Debugging to see the address method

We can find that the address of member c of the structure s is 4 different from the address of member a, which verifies that there is a waste of space and an address offset.

  • The macro offsetof calculates the offset

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<stddef.h>

struct S1
{
    char c;
    int a;
};

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

 The result of running the code is as follows:

Structure nesting problem:

struct S3
{
    double d;
    char c;
    int i;
};

struct S4
{
    char c1;
    struct S3 s3;
    double d;
};

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

Mind Diagram: 

 The result of running the code is as follows:

 Why does memory alignment exist?

  • Platform reason (reason for porting):

Not all hardware platforms can access arbitrary data at any address; some hardware platforms can only fetch certain types of data at certain addresses, otherwise a hardware exception is thrown.

  • 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.

All in all, the memory alignment of structures is a practice of exchanging space for time.

Although memory alignment can greatly improve performance by sacrificing space, it cannot be wasted mindlessly. We can still save some space by some means. When designing the structure, we must not only satisfy alignment, but also save space , it should be done: let the members who occupy a small space gather together as much as possible.

The code example is as follows:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

struct S1
{
    char a;
    int b;
    char c;
};

struct S2
{
    char a;
    char c;
    int b;
};

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

The result of the operation is as follows:


 ⚡Modify the default alignment

When the alignment of the structure is inappropriate, we can change the default alignment by ourselves. #pragma This preprocessing directive, here we use #pragma pack() again , can change our default alignment.

Basic usage of #pragma pack():

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

#pragma pack(1)  //设置默认对齐数为1
struct S1
{
    char a;
    int b;
    char c;
};
#pragma pack()  //取消设置的默认对齐数,还原为默认

struct S2
{
    char a;
    int b;
    char c;
};

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

The result of the operation is as follows:


 ⚡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(const struct S* ps)
{
    printf("%d\n", ps->num);
}
int main()
{
    print1(s); //传结构体
    print2(&s); //传地址
    return 0;
}

In the section on learning functions, we mentioned that when a function passes parameters, the parameters need to be pushed onto the stack, and there will be system overhead in terms of time and space.
Similarly, if a structure object is passed, the structure is too large, and the system overhead of pushing the parameters to the stack is relatively large, which will lead to a decrease in performance.

 Of course, both value passing and address passing have their own advantages and disadvantages. For example, the advantage of passing value is that the formal parameter is a temporary copy of the actual parameter, so the actual parameter is protected to a certain extent; To modify, you can add const to the function parameters for protection.


Thank you for reading this blog. It took a long time to create it. Friends think my blog is helpful to you. You may wish to leave your likes and favorites, follow me, and show you a different C language. 

98b76a6f4a9c4ca88fd93da1188ac6f9.gif

Guess you like

Origin blog.csdn.net/JX_BC/article/details/129927504