C语言将链表结构体封装到另一结构体的方法及段错误排查
段错误(SIGSEGV)原因分析与修复
想要实现一个隐藏底层链表细节的抽象数据类型HiddenHeaders,配套IsEmpty和Enqueue函数,让main函数的用户无需知晓底层struct listStructure的存在。但程序调用函数时直接触发SIGSEGV段错误,原始代码如下:
#include <stdio.h> #include <stdlib.h> #include <stdbool.h> struct listStructure { int data; struct listStructure *nextPtr; }; typedef struct listStructure ListStructure; struct hiddenHeaders { ListStructure *ptr_to_list; }; typedef struct hiddenHeaders HiddenHeaders ; HiddenHeaders * Enqueue (HiddenHeaders *abstract, int user_input); bool IsEmpty (HiddenHeaders *abstract); int main () { HiddenHeaders *my_list; int input; my_list = NULL; printf ("Enter data item value to add "); scanf ("%d", &input); my_list = Enqueue (my_list, input); return 0; } bool IsEmpty(HiddenHeaders *abstract) { ListStructure *adt_el = abstract->ptr_to_list; bool isEmpty; if (adt_el == NULL) { return isEmpty=true; } else return isEmpty=false; } HiddenHeaders * Enqueue (HiddenHeaders *abstract, int user_input) { ListStructure *adt_el = abstract->ptr_to_list; HiddenHeaders *abstract_Ptr = abstract; if (!IsEmpty(abstract)) { while ((adt_el -> nextPtr) == NULL) { adt_el = adt_el -> nextPtr; } adt_el -> nextPtr = (ListStructure *) malloc (sizeof (ListStructure)); adt_el = adt_el -> nextPtr; adt_el -> nextPtr = NULL; adt_el -> data = user_input; return abstract_Ptr; } else { adt_el = (ListStructure *) malloc (sizeof (ListStructure)); adt_el -> nextPtr = NULL; adt_el -> data = user_input; return abstract; } }
核心错误点
- 空指针解引用:
main中my_list初始化为NULL,调用Enqueue和IsEmpty时,函数直接尝试访问abstract->ptr_to_list——对NULL指针进行解引用,这是触发段错误的直接原因。 - 未初始化
HiddenHeaders实例:当传入的HiddenHeaders指针为NULL时,没有先创建HiddenHeaders结构体实例,直接操作其内部的链表指针,导致非法内存访问。 - Enqueue逻辑错误:
- else分支中,创建了链表节点但未将其关联到
HiddenHeaders的ptr_to_list,新节点完全游离。 - 寻找链表尾的循环条件写反:
while ((adt_el -> nextPtr) == NULL)只会在当前节点是尾节点时循环一次(且循环体里adt_el = adt_el->nextPtr会让adt_el变成NULL,后续操作再次触发错误),正确的条件应该是while (adt_el->nextPtr != NULL)来遍历到真正的尾节点。
- else分支中,创建了链表节点但未将其关联到
修复后的完整代码
#include <stdio.h> #include <stdlib.h> #include <stdbool.h> // 底层链表结构体,对用户透明 struct listStructure { int data; struct listStructure *nextPtr; }; typedef struct listStructure ListStructure; // 抽象数据类型,用户仅需操作该类型 struct hiddenHeaders { ListStructure *ptr_to_list; }; typedef struct hiddenHeaders HiddenHeaders; HiddenHeaders *Enqueue(HiddenHeaders *abstract, int user_input); bool IsEmpty(HiddenHeaders *abstract); int main() { HiddenHeaders *my_list = NULL; int input; printf("Enter data item value to add: "); scanf("%d", &input); my_list = Enqueue(my_list, input); // 测试IsEmpty功能 printf("Is list empty? %s\n", IsEmpty(my_list) ? "Yes" : "No"); return 0; } bool IsEmpty(HiddenHeaders *abstract) { // 先判断抽象对象是否为空,为空则链表一定为空 if (abstract == NULL) { return true; } // 再判断底层链表指针是否为空 return abstract->ptr_to_list == NULL; } HiddenHeaders *Enqueue(HiddenHeaders *abstract, int user_input) { // 如果抽象对象未初始化,先创建并初始化 if (abstract == NULL) { abstract = (HiddenHeaders*)malloc(sizeof(HiddenHeaders)); abstract->ptr_to_list = NULL; } // 创建新的链表节点 ListStructure *new_node = (ListStructure*)malloc(sizeof(ListStructure)); new_node->data = user_input; new_node->nextPtr = NULL; // 如果链表为空,直接作为头节点 if (IsEmpty(abstract)) { abstract->ptr_to_list = new_node; } else { // 遍历到链表尾节点 ListStructure *current = abstract->ptr_to_list; while (current->nextPtr != NULL) { current = current->nextPtr; } // 将新节点挂载到尾节点后 current->nextPtr = new_node; } return abstract; }
关键修改说明
- 空指针防护:
IsEmpty和Enqueue先判断传入的abstract是否为NULL,避免非法内存访问。 - 初始化抽象对象:当
abstract为NULL时,先分配HiddenHeaders内存并初始化其链表指针为NULL,确保后续操作的合法性。 - 修正链表遍历逻辑:将寻找尾节点的循环条件改为
current->nextPtr != NULL,正确遍历到链表末尾。 - 关联新节点:在空链表时将新节点赋值给
abstract->ptr_to_list,非空时挂载到尾节点的nextPtr,确保节点被正确加入链表。
内容的提问来源于stack exchange,提问作者AnnaKarin47
相关产品推荐
相关产品推荐

