Custom types – structures, unions, enumerations

Article directory

    • Structure
      • Structure declaration, variable creation and initialization
      • Anonymous structure declaration
      • self-referencing of structures
      • structure member access operator
      • Structure memory alignment
        • Why does memory alignment exist?
        • Modify the default alignment number
      • Structure parameter passing
      • Rank
        • Bit segment application examples
    • consortium
      • Consortium application
    • enumerate
      • Comparison with #define
      • Use of enumerations

Structure

A structure is a collection of variables, which become member variables and can be of different types.

Declaration, creation of variables and initialization of structures

struct Stu
{<!-- -->
   char name[20];
   int age;
   int score;
}Zhangsan;//The semicolon cannot be missed! ! !

struct Stu lisi = {<!-- -->"lisi", 20, 98};

At this time, Zhangsan after the semicolon uses this structure type to create a variable called Zhangsan.

It is also possible to initialize out of order:

struct Stu
{<!-- -->
   int age;
   int score;
};

struct Stu Zhangsan = {<!-- -->.score = 98, .age = 20};

Anonymous structure declaration

struct
{<!-- -->
   char name[20];
   int age;
   int score;
}Zhangsan;

This time the structure type is anonymous and can only be used once. If it is modified again, an error will be reported.

Self-reference of structure

If you define a linked list node, you cannot include yourself in the structure, otherwise the size of the structure will become infinite.

struct test
{<!-- -->
   int data;
   struct test;
} //Writing like this is wrong! !

It should be written like this:

struct test
{<!-- -->
   int data;
   struct test* next;
}

In addition, it is not possible to use the renamed name before renaming the structure type.

typedef struct test
{<!-- -->
   int data;
   Test* nect;
}Test;//This is also wrong! ! !

Structure member access operator

Both . and -> can be used to access structure members, but one is accessed using variables and the other is accessed using pointers.

Structure variable.Member variable name
Structure pointer->member variable name

struct Stu
{<!-- -->
   int age;
   int score;
};

struct Stu s = {<!-- -->.score = 98, .age = 20};
struct Stu* p = &s;
printf("%d, %d", s,score, p->age);

Structure memory alignment

Structure alignment rules:

  • The starting address of the first member of the structure relative to the structure variable offset is 0
  • Other members should be aligned to an offset that is an integer multiple of the alignment number
  • Alignment number = The smaller of the compiler’s default number and the size of the member variable
  • The default alignment number of VS is 8
  • Linux does not have a default alignment number. The alignment number is always the size of the member itself.
  • The size of a structure is always an integer multiple of the maximum alignment number of all members.
  • If a structure is nested, the members of the nested structure are aligned to an integer multiple of the largest alignment number among their own members, and the overall size is an integer multiple of the largest alignment number.

Why does memory alignment exist?
  1. Platform reason: Some hardware platforms can only access certain addresses and retrieve specific types of data.
  2. Performance reason: Data structures should be aligned on natural boundaries whenever possible. Suppose a processor always reads 8 bytes from memory. If unaligned memory is accessed, the processor needs two accesses because the class object may be divided into two 8-byte blocks; while aligned memory only Needed once.

Memory alignment is actually the practice of trading space for time. If you want to save space, you can group members that take up less space together as much as possible.

Modify the default alignment number

There is a preprocessing directive that can modify the compiler’s default alignment number.

#include<stdio.h>
#pragma pack(1)//Modify the default alignment number to 1

Structure parameter passing

When passing structure parameters, call by address is preferred, because after passing in the parameters, they need to be pushed onto the stack first. If the structure is too large, call by value may take up more space

Bit segment

The members of the bit field must be int, unsigned int or signed int. In C99, they can also be other types.

struct Stu
{<!-- -->
   int age:6;
   int score:8;
};

The above age and score are bit segment types.
It means that age occupies 6 bits and score occupies 8 bits.
The size of the bit segment cannot exceed the size of the original type itself! !

Bit fields are allocated in space in 4 bytes or 1 byte format.

Bit segments involve many uncertainties and are not cross-platform, so portable programs should avoid using bit segments.

Bit segment application examples

The format of IP data in network datagrams:

Segments can save a certain amount of space

Consortium

The declaration and access of unions are similar to structures:

union U
{<!-- -->
   char a;
   int b;
}

In the union, all members share the same space. Changing b is likely to change a.

In terms of space allocation, 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 alignment number, it will be automatically padded to an integer multiple.

Consortium application

Suppose you want to write a product list with the following contents:
Book (inventory, price, title, author)
Cup (stock, price, color)
Shirts (inventory, price, color, style)

struct list
{<!-- -->
   int stock;
   int price;
   union
   {<!-- -->
      struct
      {<!-- -->
         char name[50];
         char author[50];
      }book;
      struct
      {<!-- -->
         char color[20];
      }cup;
      struct
      {<!-- -->
         char color[20];
         char type[50];
      }shirt;
   }item;
};

Since only one item needs to be accessed at a time, putting the unique attributes of these three items into a union saves space.

Write a function to determine whether the current machine is big-endian or little-endian:

int check()
{<!-- -->
   union un
   {<!-- -->
      int a;
      char b;
   }
   un.a = 1;
   return un.c;//Big endian returns 1, little endian returns 0
}

Enumeration

As the name suggests, an enumeration is to list the possible values of a variable one by one.

enum day
{<!-- -->
   Mon;
   Tue;
   Wedn;
   Thu;
   Fri;
   Sat;
   Sun
};

When using enumerations, Mon is replaced by 0 by default, Tue is replaced by 1 by default, and so on…
You can also change the enumeration value:

enum day
{<!-- -->
   Mon = 1;
   Tue;
   Wedn;
   Thu;
   Fri;
   Sat;
   Sun
};

After changing the first one to 1, the following ones will also increase in sequence.
The elements in the enumeration can be initialized to any value, but once initialized, the elements in the enumeration are constants and cannot be modified.

Comparison with #define

Enumeration and define have the same purpose, but there are also differences:

  1. Enumeration lists the possible results one by one, which is more intuitive
  2. Enumerations have type checking while #define has no type. The compiler has stricter requirements for the use of enumerations.
  3. To facilitate debugging, #define will be deleted in the preprocessing stage.
  4. Enumerations follow scoping rules

Using enumerations

enum color
{<!-- -->
   BLUE = 1;
   RED = 3;
   GREEN = 5;
}
enum color cl1 = BLUE;

In C language, you can use integers to assign values to enumeration types, such as enum color cl1 = 1;, but this is not possible in C++.