c语言tips-结构体数组 VS 链表宏:`list_for_each_entry` 的优势与局限对比分析
0 . 写在开头
在嵌入式开发、操作系统内核或者一般的 C 语言项目中,我们经常需要管理一组结构体数据。对于初学者而言,使用结构体数组无疑是最直观的选择,数组简单、访问高效,代码逻辑也易于理解。然而,当项目变得更复杂、数据对象需要动态增删、或者不确定元素数量时,结构体数组开始显得捉襟见肘。这时,许多开发者会转向链表,尤其是在 Linux 内核中被广泛使用的宏式链表管理方式,比如
list_for_each_entry
。本文将从实际使用角度出发,对比结构体数组与基于list_for_each_entry
的内核链表遍历方式,分析它们在灵活性、性能、可维护性等方面的优劣,帮助你在实际工程中做出更合适的选择。
1. 示例代码对比
以下代码分别使用结构体数组和使用链表遍历的方式来管理一组结构体数据,分别做这种数据结构的建立和遍历
#include <stdio.h>
#include <stdlib.h>/* ------- 链表结构与宏 ------- */
struct list_head {struct list_head *next, *prev;
};#define INIT_LIST_HEAD(ptr) \do { (ptr)->next = (ptr); (ptr)->prev = (ptr); } while (0)#define list_entry(ptr, type, member) \((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))#define list_for_each_entry(pos, head, member) \for (pos = list_entry((head)->next, typeof(*pos), member); \&pos->member != (head); \pos = list_entry(pos->member.next, typeof(*pos), member))#define list_add_tail(new, head) do { \(new)->prev = (head)->prev; \(new)->next = (head); \(head)->prev->next = (new); \(head)->prev = (new); \
} while (0)/* ------- 学生结构体 ------- */
struct student {int id;struct list_head list; // 仅用于链表方式
};#define STUDENT_COUNT 5int main() {printf("== 使用结构体数组管理 ==\n");struct student students[STUDENT_COUNT];for (int i = 0; i < STUDENT_COUNT; ++i) {students[i].id = i + 1;printf("Student id: %d\n", students[i].id);}printf("\n== 使用链表管理 ==\n");struct list_head student_list;INIT_LIST_HEAD(&student_list);for (int i = 0; i < STUDENT_COUNT; ++i) {struct student *s = malloc(sizeof(struct student));s->id = i + 1;list_add_tail(&s->list, &student_list);}struct student *s;list_for_each_entry(s, &student_list, list) {printf("Student id: %d\n", s->id);}return 0;
}
输出结果如下:
== 使用结构体数组管理 ==
Student id: 1
Student id: 2
Student id: 3
Student id: 4
Student id: 5== 使用链表管理 ==
Student id: 1
Student id: 2
Student id: 3
Student id: 4
Student id: 5
我们将代码放到pythontutor运行,可以很直观的看到这两张数据结构的区别,并总结一下这两种方式的优缺点
场景 / 特性 | 结构体数组 | list_for_each_entry 链表 |
---|---|---|
元素数量固定 | ✅ 适合 | ❌ 不适合 |
动态增删元素 | ❌ 不适合 | ✅ 非常适合 |
代码简洁性 | ✅ 简单直观 | ❌ 依赖宏和嵌套结构 |
操作效率(遍历) | ✅ 更快(顺序内存) | ❌ 指针跳跃开销大 |
内存管理复杂度 | ✅ 静态分配或一块申请 | ❌ 手动分配和释放 |
实时系统 / 嵌入式开发 | ✅ 适合小系统 | ✅ 广泛用于内核、嵌入式协议栈 |