C Language Structures: From Beginner to Mastery – A Comprehensive Guide

Recent Hot Articles

C Language Learning Guide: Have You Mastered These Core Knowledge Points?

C Language Functions: From Beginner to Mastery – A Comprehensive Guide

C Language Pointers: From Beginner to Mastery – A Comprehensive Guide

C Language Arrays: From Beginner to Mastery – A Comprehensive Guide

Beginner’s Guide to Avoiding Pitfalls in C Language: Avoid These 12 Devilish Details to Double Your Efficiency!

Main Content

C Language Structures: From Beginner to Mastery - A Comprehensive Guide

Introduction

The C language structure (struct) is a powerful user-defined data type in C programming that allows us to combine different types of data together and process them as a whole. Structures play a crucial role in C language, providing us with a flexible way to organize data, enabling us to create data structures that better reflect real-world data models. This report will comprehensively and deeply explain the relevant knowledge of C language structures, from basic concepts to advanced applications, helping readers systematically master this important programming tool.

Basic Concepts of Structures

What is a Structure

A structure is a collection of data consisting of a series of data members that can be of the same or different types. Unlike basic data types (such as int, char), structures allow us to create custom data types to meet specific programming needs. The main role of structures in C language is encapsulation. The benefit of encapsulation is reusability, allowing users to use it without worrying about internal implementation details, only needing to use it according to its definition.

Why Do We Need Structures

In practical programming, we often need to handle variables that contain multiple data types. For example, a student’s information may require a student ID (string), name (string), age (integer), etc. These data types are different, but they collectively represent a whole. Structures store different types of data together, processing them as a whole. In actual projects, structures are widely used. Developers often use structures to encapsulate certain attributes to form new types. Since C language cannot operate databases, in projects, a large amount of data is stored in memory by manipulating the internal variables of structures to complete data storage and operations.

Declaration and Definition of Structures

Simple Declaration and Definition

In C language, declaring a structure uses the keyword <span>struct</span>, followed by the structure name and a list of members enclosed in curly braces.

struct Point {
    int x;
    int y;
};

After declaring a structure type, we can define a variable using this custom structure type:

struct Point p;

Simultaneous Declaration and Definition

We can also define a structure variable while declaring the structure:

struct Point {
    int x;
    int y;
} p;

In this case, p is a variable of the Point type we just defined.

Anonymous Structures

C language also allows us to define a variable directly without specifying the structure name:

struct {
    int x;
    int y;
} p = {10, 20};

In this case, we created an anonymous structure and immediately defined the variable p. However, the drawback of anonymous structures is that we cannot define other variables of the same type.

Simplifying Syntax with typedef

To simplify the use of structures, we can use the <span>typedef</span> keyword to create an alias for the structure:

typedef struct {
    int x;
    int y;
} Point;
Point p;
p.x = 10;

Thus, we can directly use Point as the type name without having to write <span>struct Point</span> every time.

Initialization of Structure Variables

Initializing All Members

We can use an initialization list enclosed in curly braces to initialize structure variables:

struct Point p = {10, 20};

The order of initialization must match the order of the structure member declarations.

Partial Member Initialization (C99 and Later)

Starting from the C99 standard, we can use designated initializers to initialize specific members of a structure without having to initialize all members in order:

struct Point p = {.x = 10, .y = 20};

This method is more flexible, especially when dealing with large structures.

Comparison of Different Initialization Methods

Here are several different initialization methods and their characteristics:

  1. Full Initialization (must provide values for all members in order):
struct Point p = {10, 20};
  1. Designated Initialization (C99 and later):
struct Point p = {.x = 10, .y = 20};
  1. Simplified Initialization (C99 and later, omitting curly braces):
struct Point p = {10};

In this case, only the first member is explicitly initialized, while other members are implicitly set to zero.

Member Access Methods

Accessing with the Dot Operator

For structure variables, we can use the dot operator (.) to access their members:

struct Point p;
p.x = 10;
p.y = 20;

Accessing with Pointers

When we have a pointer to a structure, we can use the arrow operator (->) to access structure members:

struct Point *ptr = &p;
ptr->x = 10;
ptr->y = 20;

Using pointers to access structure members is a common practice in C language, especially when dealing with large structures or needing dynamic memory allocation.

Memory Usage of Structures

Rules for Calculating Structure Size

The size of a structure is not simply the sum of its members. In C language, memory allocation for structures follows specific alignment rules.

Memory Alignment Rules

  1. Data Member Alignment Rules: The data members of a structure (or union) have the first data member placed at an offset of 0, and subsequent data members are aligned according to the smaller of the value specified by the compiler and the length of that data member.
  2. Overall Structure Alignment Rules: After the data members have completed their alignment, the structure itself must also be aligned, which will be according to the smaller of the value specified by the compiler and the length of the largest data member.
  3. Conclusion: When the alignment factor specified by the compiler is equal to or exceeds the length of all data members, the size of this factor will have no effect.

Memory Alignment Example

Here is an example of memory alignment:

struct {
    char a;    // occupies 1 byte but will be padded to 4 bytes
    int b;     // occupies 4 bytes
} s;

In this example, the char type a will actually occupy 4 bytes (including 3 padding bytes), while the int type b occupies 4 bytes, so the total size of the structure is 8 bytes, not 1 + 4 = 5 bytes.

Memory Optimization Techniques

To reduce the memory usage of structures, consider the following techniques:

  1. Place Smaller Members First: This can reduce padding bytes caused by alignment.
    struct {
        char a; // placed first
        int b;
    } s; // may occupy 8 bytes
    

    Compared to

    struct {
        int b;
        char a;
    } s; // may occupy 4 bytes
    
  2. Use #pragma pack Directive: This can adjust the compiler’s alignment factor to reduce padding bytes.
    #pragma pack(1)  // disable alignment
    struct {
        char a;
        int b;
    } s;
    #pragma pack()   // restore default alignment
    

Nesting of Structures

Basic Concept of Nested Structures

Structures can contain other structures as their members, which is called a nested structure. Nested structures allow us to create more complex data models.

struct Date {
    int day;
    int month;
    int year;
};
struct Employee {
    char name[50];
    struct Date birth_date;
};
struct Employee e;
e.birth_date.day = 15;

In this example, the Employee structure contains a member birth_date of type Date, which itself is a structure containing day, month, and year.

Anonymous Nested Structures

We can also use anonymous structures to nest structures:

struct Employee {
    char name[50];
    struct {
        int day;
        int month;
        int year;
    } birth_date;
};
struct Employee e;
e.birth_date.day = 15;

In this case, birth_date is an anonymous Date structure, and we can still use the dot operator to access its members.

Structure Pointers

Basic Usage of Structure Pointers

A structure pointer is a pointer that points to a structure variable. We can use structure pointers to access structure members.

struct Point p = {10, 20};
struct Point *ptr = &p;
printf("x: %d, y: %d\n", ptr->x, ptr->y);

Manipulating Structures via Pointers

Using structure pointers allows for more flexible manipulation of structures, especially when dynamic memory allocation is needed or when passing large structures.

struct Point *ptr = malloc(sizeof(struct Point));
if (ptr == NULL) {
    // handle memory allocation failure
    exit(EXIT_FAILURE);
}
ptr->x = 10;
ptr->y = 20;
free(ptr); // free memory

Structure Pointers as Function Parameters

Passing structure pointers as function parameters can avoid copying the entire structure, improving efficiency.

void printPoint(struct Point *p) {
    printf("x: %d, y: %d\n", p->x, p->y);
}
struct Point p = {10, 20};
printPoint(&p);

Structure Arrays

One-Dimensional Structure Arrays

We can create structure arrays to store multiple structure variables in one array.

struct Point points[10]; // define an array containing 10 Point elements
points[0].x = 10;       // access the x member of the first element

Initialization of Structure Arrays

We can also initialize structure arrays:

struct Point points[] = {
    {10, 20},
    {30, 40},
    {50, 60}
};

Flexible Array Member

C99 introduced the concept of flexible arrays, allowing us to declare arrays of variable size within structures.

struct FlexArray {
    int len;
    int arr[]; // flexible array
};
struct FlexArray *fa = malloc(sizeof(struct FlexArray) + 10 * sizeof(int));
fa->len = 10;

Characteristics of flexible arrays:

  1. Flexible arrays must be the last member of the structure
  2. The size of the structure is indeterminate at compile time
  3. Memory must be dynamically allocated, specifying the size of the array

Structures and Functions

Functions Receiving Structures as Parameters

Functions can receive structures as parameters, but it should be noted that this will cause the entire structure to be copied, which may not be efficient for large structures.

void printPoint(struct Point p) {
    printf("x: %d, y: %d\n", p.x, p.y);
}
struct Point p = {10, 20};
printPoint(p); // the entire structure is copied

Functions Receiving Structure Pointers

To improve efficiency, we usually let functions receive structure pointers as parameters.

void printPoint(struct Point *p) {
    printf("x: %d, y: %d\n", p->x, p->y);
}
struct Point p = {10, 20};
printPoint(&p); // only pass the pointer, no copying of the entire structure

Functions Returning Structures

Functions can also return structures, but it should be noted that what is returned is a copy of the structure, not a pointer.

struct Point createPoint(int x, int y) {
    struct Point p;
    p.x = x;
    p.y = y;
    return p;
}
struct Point p = createPoint(10, 20);

Structures and Memory Management

Statically Allocated Structures

Statically allocated structures allocate memory at compile time, and their lifetime lasts until the program ends.

struct Point p; // statically allocated, usually on the stack

Dynamically Allocated Structures

Dynamically allocated structures use functions like malloc, calloc, etc., to allocate memory on the heap, requiring manual memory management.

struct Point *p = malloc(sizeof(struct Point)); // dynamically allocated
if (p == NULL) {
    // handle memory shortage
}
p->x = 10;
p->y = 20;
free(p); // free memory

Memory Management of Structure Arrays

For structure arrays, we can dynamically allocate memory, specifying the size of the array.

int n = 10;
struct Point *points = malloc(n * sizeof(struct Point));
if (points == NULL) {
    // handle memory shortage
}
for (int i = 0; i < n; i++) {
    points[i].x = i * 10;
    points[i].y = i * 20;
}
free(points);

Advanced Applications of Structures

Structures and Linked Lists

Structures can be used to create linked lists, a commonly used data structure in C language.

struct Node {
    int data;
    struct Node *next;
};
struct Node *head = NULL;
// Add node to the head of the linked list
void addNode(int data) {
    struct Node *newNode = malloc(sizeof(struct Node));
    if (newNode == NULL) {
        return;
    }
    newNode->data = data;
    newNode->next = head;
    head = newNode;
}
// Traverse the linked list
void printList() {
    struct Node *current = head;
    while (current != NULL) {
        printf("%d ", current->data);
        current = current->next;
    }
    printf("\n");
}

Structures and File Operations

Structures can be used to organize data in files, making file reading and writing more convenient.

struct Student {
    char name[50];
    int age;
    float grade;
};
// Write structure to file
void writeStudentToFile(const struct Student *s, const char *filename) {
    FILE *file = fopen(filename, "wb");
    if (file == NULL) {
        printf("Cannot open file\n");
        return;
    }
    fwrite(s, sizeof(struct Student), 1, file);
    fclose(file);
}
// Read structure from file
void readStudentFromFile(struct Student *s, const char *filename) {
    FILE *file = fopen(filename, "rb");
    if (file == NULL) {
        printf("Cannot open file\n");
        return;
    }
    fread(s, sizeof(struct Student), 1, file);
    fclose(file);
}

Structures and Object-Oriented Programming

Although C language is not an object-oriented language, we can use structures to simulate object-oriented programming concepts such as encapsulation and message passing.

// Define a point structure
struct Point {
    int x;
    int y;
};
// Define a function to move the point
void movePoint(struct Point *p, int deltaX, int deltaY) {
    p->x += deltaX;
    p->y += deltaY;
}
// Usage
struct Point p = {10, 20};
movePoint(&p, 5, 5);
printf("New coordinates are (%d, %d)\n", p.x, p.y);

Differences Between Structures and Unions

Basic Concept of Unions

A union is a special type of structure where all members share the same memory location. At any given time, a union can only store the value of one of its members.

union Data {
    int i;
    float f;
    char c;
};

Main Differences Between Structures and Unions

  1. Memory Usage:
  • Members of a structure are stored contiguously, with each member having its own memory space.
  • All members of a union share the same memory space.
  • Access Method:
    • Members of a structure can be accessed simultaneously.
    • Members of a union cannot be accessed simultaneously because they share the same memory space.
  • Usage Scenarios:
    • Structures are suitable for situations where multiple different types of data need to be stored simultaneously.
    • Unions are suitable for situations where only one of the different types of data needs to be stored at a time, which can save memory.

    Differences Between Structures and Classes

    Structures and Classes in C and C++

    There are some differences between structures in C language and classes in C++:

    1. Default Access Level:
    • In C language, all members of a structure are public by default.
    • In C++, all members of a class are private by default.
  • Inheritance:
    • C language does not support inheritance.
    • C++ supports inheritance, which is one of the core features of object-oriented programming.
  • Member Functions:
    • Structures in C language cannot contain member functions.
    • Classes in C++ can contain member functions.

    Design Differences Between Structures and Classes

    1. Encapsulation:
    • Structures emphasize the collection of data, with weaker encapsulation.
    • Classes emphasize the encapsulation of data and behavior, with stronger encapsulation.
  • Level of Abstraction:
    • Structures are closer to the low level and have a more direct relationship with hardware.
    • Classes are more abstract and can better model objects in the real world.

    Applications of Structures in Practical Programming

    Data Structure Implementation

    Structures are the foundation for implementing various data structures, such as linked lists, trees, graphs, etc.

    // Define a binary tree node structure
    struct TreeNode {
        int value;
        struct TreeNode *left;
        struct TreeNode *right;
    };
    // Create a binary tree node
    struct TreeNode *createNode(int value) {
        struct TreeNode *node = malloc(sizeof(struct TreeNode));
        if (node == NULL) {
            return NULL;
        }
        node->value = value;
        node->left = NULL;
        node->right = NULL;
        return node;
    }
    

    System Programming

    In system programming, structures are widely used to interact with operating system APIs.

    // Using the RECT structure from Windows API
    typedef struct _RECT {
        LONG left;
        LONG top;
        LONG right;
        LONG bottom;
    } RECT, *PRECT;
    RECT rect;
    rect.left = 0;
    rect.top = 0;
    rect.right = 100;
    rect.bottom = 100;
    

    Network Programming

    In network programming, structures are used to organize network packets.

    // Define a simple network packet structure
    struct NetworkPacket {
        unsigned short type;
        unsigned short length;
        unsigned char data[100];
    };
    // Create a network packet
    struct NetworkPacket packet;
    packet.type = 0x01;
    packet.length = 10;
    // Fill data
    

    Common Pitfalls and Best Practices for Structures

    Common Pitfalls

    1. Member Access Out of Bounds: Accessing non-existent structure members.
    struct Point p;
    p.z = 10; // Error, Point structure has no z member
    
    1. Memory Leak: Forgetting to free dynamically allocated structure memory.
    struct Point *p = malloc(sizeof(struct Point));
    p->x = 10;
    // No free(p) leads to memory leak
    
    1. Dangling Pointer: Using already freed memory or uninitialized pointers.
    struct Point *p = malloc(sizeof(struct Point));
    free(p); // freed memory pointed by p
    p->x = 10; // Error, p is now a dangling pointer
    
    1. Incorrect Structure Size Calculation: Forgetting to consider compiler memory alignment.
    struct {
        char a;
        int b;
    } s;
    // sizeof(s) may be 8 instead of 5
    

    Best Practices

    1. Use typedef to Simplify Syntax: Create short aliases for structures.
    typedef struct {
        int x;
        int y;
    } Point;
    
    1. Organize Related Data Together: Use structures to encapsulate related data.
    struct Student {
        char name[50];
        int age;
        float grade;
    };
    
    1. Avoid Overly Large Structures: Break down large data structures into smaller structures.
    2. Use Pointers Wisely: For large structures, use pointers instead of value passing.
    3. Pay Attention to Memory Management: Ensure correct allocation and deallocation of dynamic memory.

    Advanced Techniques for Structures

    Anonymous Structures

    Anonymous structures can simplify code, especially when dealing with hierarchical data.

    struct Employee {
        char name[50];
        struct { // anonymous structure
            int day;
            int month;
            int year;
        } birth_date;
    };
    struct Employee e;
    e.birth_date.day = 15; // directly access members of the anonymous structure
    

    Structures with the Same Prefix

    When multiple structures have the same initial members, they can be designed to have the same prefix, allowing for interchangeable use in certain cases.

    struct Person {
        char name[50];
        int age;
    };
    struct Student {
        char name[50];
        int age;
        float grade;
    };
    // These two structures have the same prefix and can interchangeably use the first two members
    

    Structure Alignment Optimization

    By adjusting member order and using compiler directives, the memory usage of structures can be optimized.

    // Original structure, may occupy 12 bytes
    struct {
        char a;    // 1 byte
        int b;     // 4 bytes, but due to alignment, has 3 bytes padding
        short c;   // 2 bytes, has 2 bytes padding to 4-byte boundary
    } s;
    // Optimized structure, may occupy 8 bytes
    struct {
        int b;     // 4 bytes
        short c;   // 2 bytes, no padding
        char a;    // 1 byte, has 1 byte padding to 4-byte boundary
    } s_optimized;
    // Use compiler directives to adjust alignment
    #pragma pack(1)  // disable alignment
    struct {
        char a;
        int b;
        short c;
    } s_packed;
    #pragma pack()   // restore default alignment
    

    Differences in Structures Across Compilers and Platforms

    Compiler Differences

    Different compilers may handle structures slightly differently, especially regarding memory alignment and flexible arrays.

    1. gcc and clang: In most cases, these two compilers handle structures similarly.
    2. msvc: Microsoft’s compiler differs in some aspects, such as support for #pragma pack directives.

    Platform Differences

    Different implementations of C language on various platforms may handle structures differently.

    1. 32-bit vs 64-bit Systems: On 64-bit systems, the size of pointers and certain data types may differ.
    2. Different Byte Orders: In big-endian and little-endian systems, the storage of multi-byte data differs. To write portable code, pay attention to the following points:
    3. Use Standard Data Types: Use fixed-size integer types from stdint.h.
    #include <stdint.h>
    uint32_t value = 0x12345678;
    
    1. Avoid Relying on Specific Platform Structure Layouts: Do not directly access the memory of structures, but rather through defined interfaces.
    2. Use Conditional Compilation: Handle differences using conditional compilation based on different platforms.
    #ifdef _WIN32
    // Implementation for Windows platform
    #elif __linux__
    // Implementation for Linux platform
    #endif
    

    Future Trends of Structures

    Evolution of C Language Standards

    The C language standard is continuously evolving, with each new version adding new features to structures.

    1. C99: Introduced flexible arrays, designated initializers, and compound literals.
    2. C11: Added memory alignment control and atomic types, which are related to structures.
    3. C23: The latest version of the C language standard, adding more new features such as concepts and modules, which will affect the use of structures.

    The Role of Structures in Modern Programming

    As programming languages and paradigms continue to evolve, the role of structures is also changing.

    1. Integration with Object-Oriented Programming: Although C language is not an object-oriented language, structures can be used with functions to simulate object-oriented programming.
    2. Combination with Generic Programming: Through macros and compiler features, generic operations on structures can be achieved to some extent.
    3. Combination with Concurrent Programming: Structures can be combined with threads and synchronization mechanisms to achieve concurrent programming.

    Conclusion

    C language structures are a powerful data organization tool that allows us to combine different types of data together and process them as a whole. Through this report’s detailed explanation, we systematically learned about the basic concepts, declaration and definition, initialization, member access, memory management, nesting, pointers, arrays, function interaction, and advanced applications of structures. Structures play a crucial role in C language, providing us with a flexible way to organize data, enabling us to create data structures that better reflect real-world data models. Whether for simple data collections or complex data structures, structures can handle it. In practical programming, the application of structures is very extensive, from the implementation of data structures to system programming, network programming, etc. By using structures wisely, we can write more efficient and maintainable code. As the C language standard continues to evolve and programming paradigms develop, the role of structures is also changing. Although C language is not an object-oriented language, by combining structures with functions, we can simulate object-oriented programming concepts such as encapsulation and message passing. In summary, mastering C language structures is an essential skill for every C language programmer. Through this study, we hope you can systematically grasp the relevant knowledge of structures and flexibly apply them in practical programming.

    References

    [1] The Most Comprehensive Explanation of C Language Structures (struct) (Ten Thousand Words of Content). https://zhuanlan.zhihu.com/p/520770506.

    If you find this article helpful, please like and share it with more like-minded people. Thank you!

    Leave a Comment