Advanced Structures

Advanced structs are like decorating a house — it's not just about building a frame, but also about efficient space usage (alignment), saving materials (bit-fields), and flexible expansion (flexible arrays).

Advanced Struct Usage with Functions

Functions That Return a Struct

C allows functions to return a struct directly — the compiler handles the copying:

C
struct Point make_point(int x, int y) {
    struct Point p = {x, y};
    return p;
}

int main(void) {
    struct Point pt = make_point(3, 4);
    printf("(%d, %d)\n", pt.x, pt.y);
    return 0;
}
💡 Tip: Returning a small struct by value is fine. For large structs, pass a pointer and fill it to avoid copy overhead.

Filling a Struct Through a Pointer

C
void fill_student(struct Student *s, const char *name, int age, float score) {
    strncpy(s->name, name, 19);
    s->name[19] = '\0';
    s->age = age;
    s->score = score;
}

int main(void) {
    struct Student stu;
    fill_student(&stu, "Zhang", 20, 88.5);
    return 0;
}

const Protection for Struct Parameters

To prevent a function from modifying a struct, use const on the pointer parameter:

C
void print_student(const struct Student *s) {
    printf("%s %d %.1f\n", s->name, s->age, s->score);
}
⚠️ Note: const struct Student *s means you cannot modify the pointed-to struct through s, but s itself can point to something else.

typedef for Type Aliases

typedef creates an alias for a type, reducing the need to repeatedly write the struct keyword.

Basic Usage

C
typedef struct {
    char name[20];
    int age;
    float score;
} Student;

Student s1 = {"Zhang", 20, 89.5};
Student *ps = &s1;
💡 Tip: No need to write struct Student anymore — just use Student directly.

typedef with Struct Pointers

C
typedef struct Node {
    int data;
    struct Node *next;
} Node, *NodePtr;

Node n1 = {10, NULL};
NodePtr head = &n1;
⚠️ Note: When a struct references itself, you must still use struct Node *next because the alias isn't in effect yet at that point.

Other Uses of typedef

C
typedef unsigned char Byte;
typedef int (*Comparator)(const void *, const void *);

Byte flag = 0xFF;
Comparator cmp = my_compare;

Example

C
#include <stdio.h>
#include <string.h>

typedef struct {
    char title[50];
    int pages;
    float price;
} Book;

Book create_book(const char *title, int pages, float price) {
    Book b;
    strncpy(b.title, title, 49);
    b.title[49] = '\0';
    b.pages = pages;
    b.price = price;
    return b;
}

void discount(Book *b, float rate) {
    b->price *= rate;
}

void print_book(const Book *b) {
    printf("<%s> %d pages $%.2f\n", b->title, b->pages, b->price);
}

int main(void) {
    Book b1 = create_book("C Programming", 320, 59.0);
    print_book(&b1);
    discount(&b1, 0.8);
    print_book(&b1);
    return 0;
}
▶ Try it Yourself
TEXT
<C Programming> 320 pages $59.00
<C Programming> 320 pages $47.20

Struct Memory Alignment

Struct members are not tightly packed in memory — the compiler inserts padding bytes according to alignment rules.

Alignment Rules

  1. Each member's offset must be a multiple of that member's size
  2. The total size of the struct must be a multiple of the largest member's size
C
struct Align1 {
    char a;
    int b;
    char c;
};

struct Align2 {
    char a;
    char c;
    int b;
};
C
printf("%zu\n", sizeof(struct Align1));
printf("%zu\n", sizeof(struct Align2));
TEXT
12
8

Layout of Align1: a (1 byte) + 3 bytes padding + b (4 bytes) + c (1 byte) + 3 bytes padding = 12.

Layout of Align2: a (1 byte) + c (1 byte) + 2 bytes padding + b (4 bytes) = 8.

💡 Tip: Grouping smaller members together reduces padding bytes — a practical technique for saving memory.

#pragma pack

You can specify the alignment boundary to compress a struct:

C
#pragma pack(push, 1)
struct Packed {
    char a;
    int b;
    char c;
};
#pragma pack(pop)

printf("%zu\n", sizeof(struct Packed));
TEXT
6
⚠️ Note: Packed alignment reduces access efficiency and can even cause errors on some platforms. Only use it in scenarios that strictly require byte alignment, such as protocol parsing or file format handling.

Bit-Fields

Bit-fields allocate space to members on a bit-by-bit basis, saving memory.

C
struct Flags {
    unsigned int ready : 1;
    unsigned int error : 1;
    unsigned int mode  : 3;
    unsigned int       : 0;
    unsigned int count : 12;
};
C
struct Flags f = {1, 0, 5, 1024};
printf("ready=%u error=%u mode=%u count=%u\n", f.ready, f.error, f.mode, f.count);
printf("Struct size: %zu\n", sizeof(f));
TEXT
ready=1 error=0 mode=5 count=1024
Struct size: 8
⚠️ Note: Bit-field layout is compiler-dependent and not portable. You cannot take the address of a bit-field member (&f.ready is illegal).

Flexible Array Members

C99 allows the last member of a struct to be an array of length 0, called a Flexible Array Member.

C
typedef struct {
    int len;
    int data[];
} IntVec;

When using it, allocate extra space as needed:

C
int n = 5;
IntVec *v = (IntVec *)malloc(sizeof(IntVec) + sizeof(int) * n);
v->len = n;
for (int i = 0; i < n; i++) {
    v->data[i] = i * 100;
}
for (int i = 0; i < v->len; i++) {
    printf("%d ", v->data[i]);
}
free(v);
TEXT
0 100 200 300 400
💡 Tip: Flexible array members are commonly used to implement variable-length structs. Compared to using a pointer to separate memory, the data is contiguous and a single free suffices.

⚠️ Note: The flexible array must be the last member, and the struct must have at least one other member. You cannot use sizeof to get the size of a flexible array.

❓ FAQ

Q What's the difference between typedef and #define for type aliases?
A typedef is processed by the compiler, follows scope rules, and handles pointer types correctly. #define is a preprocessor text substitution that can cause subtle errors. Prefer typedef.
Q Why does memory alignment exist?
A CPUs read memory more efficiently at aligned boundaries, and some architectures fault on unaligned access. The compiler automatically adds padding for compatibility and performance.
Q Can you take the address of a bit-field?
A No. A bit-field member may be smaller than a byte and has no independent address. The & operator cannot be used on bit-fields.
Q Flexible array or pointer member — which is better?
A Flexible arrays store data contiguously with a single allocation and deallocation, and are cache-friendly. Pointer members can point anywhere, offering more flexibility but requiring two allocations and two frees. Flexible arrays are the simpler choice.

📖 Summary

📝 Exercises

  1. Define a struct containing char, short, int, and double. Try two different member orderings and use sizeof to verify the size difference
  2. Use typedef to define a linked list node type, then write functions to create and traverse a singly linked list
  3. Use a flexible array member to implement a dynamic string struct (with len and data[]), supporting an append-character operation
100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏