“Understanding custom types: structures, enumerations, unions”

Foreword

This article will introduce three custom types in C language: structure, enumeration and union. These data types are very important in C language, they allow programmers to create their own data types to meet specific needs. By understanding these data types, you can better grasp the characteristics and applications of C language.

1. Structure

Basic knowledge of structures

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

Note: Unlike an array, an array is a collection of elements of the same type, and the structure member types can be different.

1 Structure declaration

struct
tag

{

member

list
;
//Member name

}
variable

list
;
//variable name

For example, describe a student:

struct Stu
{
 char name[20];//name
 int age;//Age
 char sex[5];//Gender
 char id[20];//student number
}; //The semicolon cannot be lost
1.2 Special Statement
  • When declaring a structure, you can declare it incompletely.

//
Anonymous structure type

struct
//The structure omits the structure tag when declaring it

{

int
a
;

char
b
;

float
c
;

}
x
;

struct
//The structure omits the structure tag when declaring it

{

int
a
;

char
b
;

float
c
;

}
a
[
20
],
*
p
;

So here’s the problem?

//
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’s illegal.

1.3 Self-reference of structure
  1. Is it okay to include a member in a structure that is of type the structure itself?

like:

//
code
1

struct
Node

{

int
data
;

struct
Node next
;

};

//
Is it possible?

If possible, then
sizeof
(
struct
Node
)
how many?

Warning: This way of writing is not possible, it is similar to the recursive behavior of functions; structures
struct
Node
It will continue to expand and eventually cause stack overflow. so
sizeof(struct Node
) cannot be calculated.

Correct way to self-reference:

//
code
2

struct
Node

{

int
data
;

struct
Node
*
next
;
//The linked list in the data structure records the position of the next structure (node)

};

2. Special errors:

//
code
3

typedef struct

{

int
data
;

Node
*
next
;

}
Node
;

//
Is it possible to write code like this?

warn:
typedef
Even if the type name redefinition has not been executed, a warning of undefined identifier will be reported.

//
solution:

typedef struct
Node

{

int
data
;

struct
Node
*
next
;

}
Node
;

1.4 Definition and initialization of structure variables

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

Method 1: Declare the variable in the main function after defining the type without initializing it

struct
Point

{

int
x
;

int
y
;

}
p1
;
//
Declare the type and define the variable at the same time
p1

struct
Point p2
;
//
Define structure variables
p2

Method 2: Declare variable initialization in the main function after defining the type

//
Initialization: Define a variable and assign an initial value at the same time.

struct
Point p3
=
{
x
,
y
};

struct
Stu
//
type declaration

{

char
name
[
15
];
//
name

int
age
;
//
age

};

struct
Stu s
=
{
“zhangsan”
,
20
};
//
initialization

Method 3: Nested initialization of the structure after defining the type

struct
Node

{

int
data
;

struct
Point p
;

struct
Node
*
next
;

}
n1
=
{
10
, {
4
,
5
},
NULL
};
//
Structure nested initialization

struct
Node n2
=
{
20
, {
5
,
6
},
NULL
};
//
Structure nested initialization

1.5 Structure memory alignment

We have mastered the basic use of structures.

Now let’s delve into a problem: calculating the size of a structure.

This is also a particularly popular test site:
Structure memory alignment

1.5.1 How to calculate ?

First, you must master the alignment rules of structures:

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

2.
Other member variables should be aligned to addresses that are integer multiples of a certain number (alignment number).

Number of alignments
=
The compiler defaults an alignment number equal to the size of the member
Smaller value
.

VS
The default value in
8
Different compilers use different default values

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 the structure is nested, the nested structure is aligned to an integer multiple of its maximum alignment number, and the overall size of the structure is all
Maximum number of alignments
(Including the number of alignments of nested structures)
an integer multiple of
.

Now that you have learned how to calculate, let’s try it out in practice.

struct S2
{
    char c1;
    char c2;
    int i;
};
printf("%d\
", sizeof(struct S2));
//The space size is 8 bytes
1.5.2 Why does memory alignment exist?

Most of the reference materials say this:

1.
Platform reasons
(
Reason for transplantation
)
:

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 will be thrown.

2.
Performance reasons
:

Data structures (especially stacks) should be aligned on natural boundaries whenever 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.

In general:

The memory alignment of the structure is
Space
in exchange for
Time
way of doing.

When designing a structure, we must not only satisfy alignment but also save space. How to do this:

//
For example:

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.

So when designing a structure:
It would be a wise choice to keep members who take up less space together as much as possible! .

1.6 Modify the default alignment number

we’ve met before
#pragma

This preprocessing directive, which we use again here, changes our default alignment number.

#include

#pragma pack(8)
//
Set the default alignment number to
8

struct
S1

{

char
c1
;

int
i
;

char
c2
;

};

#pragma pack()
//
Cancel the set default alignment number and restore it to default

#pragma pack(1)
//
Set the default alignment number to
1

struct
S2

{

char
c1
;

int
i
;

char
c2
;

};

#pragma pack()
//
Cancel the set default alignment number and restore it to default

int
main
()

{

//
What is the output?

printf
(
“%d\

,
sizeof
(
struct
S1
));

printf
(
“%d\

,
sizeof
(
struct
S2
));

return
0
;

}

Conclusion:

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

Exercise:

Write a macro to calculate the offset of a variable in the structure relative to the first address and give an explanation

Inspection:
offsetof
Macro implementation

#define

my_offsetof
(type,name) ((
size_t)
((
char*
)( & amp;(( ( (
struct S2
*)
NULL
)->name))-(char*)
NULL
))

//This method uses a null pointer to convert it into a structure pointer of type type, then accesses the members to obtain its address, and then subtracts it from the starting address of the structure.

1.8 Structure parameter passing

Which of the following print1 or print2 functions is better?

struct
S

{

int
data
[
1000
];

int
num
;

};

struct
S s
=
{{
1
,
2
,
3
,
4
},
1000
};

//
Structure parameter passing

void
print1
(
struct
S s
)

{

printf
(
“%d\

,
s
.
num
);

}

//
Structure address passing parameters

void
print2
(
struct
S
*
PS
)

{

printf
(
“%d\

,
PS
->
num
);

}

int
main
()

{

print1
(
s
);
//
Pass structure

print2
(
&
s
);
//
Pass address

return
0
;

}

The answer is: preferred
print2
function.

Because

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

2. Position

After talking about structures, we have to talk about the ability of structures to realize bit segments.

2.1 What is a bit segment

The declaration and structure of bit fields are similar, with two differences:

  1. Members of a bit field must be int, unsigned int, or signed int.
  2. The member name of the bit field is followed by a colon and a number.

for example:

struct
A
//A is a bit segment type
.

{

int
_a
:
2
;

int
_b
:
5
;

int
_c
:
10
;

int
_d
:
30
;

};

2.2 Memory allocation of bit segments
  1. 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 allocated in 4 bytes (int) or 1 byte (char) as needed.
  3. Bit segments involve many uncertainties. Bit segments are not cross-platform. Programs that focus on portability should avoid using bit segments.

Demo:

struct S
{
    char a:3;
    char b:4;
    char c:5;
    chard:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
2.3 Cross-platform issues with bit segments

1. int
It is undefined whether a bit field is treated as a signed or unsigned number.

2.
The maximum number of bits in a bit field cannot be determined. (
16
Bit machine maximum
16
,
32
Bit machine maximum
32
, written as
27
,exist
16
bit machine

There will be problems with the machine.

3.
The criteria for whether members of a bit field are allocated from left to right or right to left in memory are undefined.

4.
When a structure contains two bit fields, and the members of the second bit field are larger and cannot fit in the remaining bits of the first bit field, it is

It is uncertain whether to discard the remaining bits or to use them.

Summary:

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

3. Enumeration

Enumeration, as the name suggests, means enumerating items one by one.

List the possible values one by one.

For example, in our real life:

Monday to Sunday of the week is limited
7
God, you can list them all.

Enumerations can be used here.

3.1 Definition of enumeration types

enum
ttype-name
//
Enumeration type name

{

//Member name

};
//variable name

The enumeration type starts from 0 by default and increments by 1 at a time. Of course, an initial value can also be assigned when defining.

enum
Color
//
color

{

RED
=
1
,

GREEN
=
2
,

BLUE
=
4

};

Note: Enumeration type members will continue to be automatically incremented by 1 from the member you assigned.

3.2 Advantages of enumerations

We can use #define to define constants, why do we have to use enumerations?

Advantages of enumerations:

  1. Increase code readability and maintainability
  2. Compared with identifiers defined by #define, enumerations have type checking, which is more rigorous.
  3. Prevented naming pollution (encapsulation)
  4. Easy to debug
  5. Easy to use, multiple constants can be defined at one time

IV. 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 union is also called a union).

for example:

//
union type declaration

union
Un

{

char
c
;

int
i
;

};

//
Definition of joint variables

union
Un un
;

//
Calculate the size of two variables

printf
(
“%d\

,
sizeof
(
un
));

4.2 Union Characteristics

The members of a union share the same memory space, so the size of a union variable is at least the size of the largest member (because the union variable

The union must be able to save at least the largest member).

4.3 Calculation of union 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 number, it must be aligned to an integer multiple of the maximum alignment number.

for example:

union
Un1

{

char
c
[
5
];

int
i
;

};

union
Un2

{

short
c
[
7
];

int
i
;

};

//
What is the output below?

printf
(
“%d\

,
sizeof
(
union
Un1
));
//The size is 8 bytes

printf
(
“%d\

,
sizeof
(
union
Un2
));
//The size is 16 bytes