C语言链表实现日历规划器:预约描述无法显示问题求助
问题描述
使用C语言实现基于链表的日历规划器,要求按时间顺序插入预约,但打印链表时无法显示预约的描述信息。且不能修改任何结构体定义,编译命令为gcc -g -Wall -pedantic-errors main.c。
原始代码
#include <stdio.h> #include <time.h> #include <stdlib.h> #include <stdbool.h> // Appointment typedef struct { time_t start; char *description; } Appointment; // List element typedef struct Element { Appointment *appointment; struct Element *next; } Element; // List of appointments typedef struct { Element *head, *tail; } List; void printAppointment(Appointment *appointment) { /* Print the Appointment structure */ char *description = appointment->description; time_t startTime = appointment->start; printf("Appointment(%s, %ld)\n", description, startTime); } Appointment createAppointment(char *description, time_t start) { Appointment appointment = {0}; appointment.description = description; appointment.start = start; return appointment; } List createList() /* Create a linked list with a head and a tail element */ { Element *head; Element *tail; head = malloc(sizeof(Element)); tail = malloc(sizeof(Element)); head->next = tail; List *list; list = malloc(sizeof(List)); list->head = head; list->tail = tail; return *list; } void clearList(List list) /* Free the list from all memory allocated, but keep the head and the tail */ { Element *temp; // Free elements as long as the head is not pointing to the tail while (list.head->next != list.tail) { // Extract the element after the head into temp, point head at the element after temp = list.head->next; list.head->next = temp->next; // Free the exctracted element free(temp); } } void debugList(List list) /* Print the list to the console */ { struct Element *current = list.head->next; while (current->appointment != NULL) { printf("%s ", current->appointment->description); current = current->next; } printf("\n"); } void insertElement(List list, char *description, time_t start) /* Insert a new element into the list based on its value */ { printf("Inserting element %s with start time %ld\n", description, start); Element *new_element = malloc(sizeof(struct Element)); Appointment new_appointment = createAppointment(description, start); new_element->appointment = &new_appointment; new_element->next = NULL; if (list.head->next == list.tail) { // List is empty, insert new element after the head printf("Head is pointing to tail, inserting %s in the middle\n", description); new_element->next = list.tail; list.head->next = new_element; return; } else { // Find the correct position for the new node in the list struct Element *current = list.head->next; struct Element *prev = list.head; int index = 0; while (1) { // If the new value is bigger than the current value, insert it before the current element if (current->appointment->start >= start) { printf("Inserting element %s at position %d\n", description, index); prev->next = new_element; new_element->next = current; return; } // Iterate forwards prev = current; current = current->next; index++; // If the new element is bigger than all elements, insert it at the end if (current == list.tail) { prev->next = new_element; new_element->next = list.tail; return; } } } } Element *findElement(List list, char *description) { /* Find a element in the list based on its value. Returns nullptr if the element is not found. */ Element *current = list.head->next; while (current->next != list.tail) { Appointment currentAppointment = *current->appointment; printf("Appointment(%s, %ld)\n", currentAppointment.description, currentAppointment.start); return NULL; } printf("Returning NULL, no element found!"); return NULL; } _Bool deleteElement(List list, char *description) { /* Find a element in the list based on its value and delete it. Returns true if the element has been deleted. */ Element *current = list.head->next; Element *prev = list.head; while (current->next != list.tail) { char *currentDescription = current->appointment->description; if (*currentDescription == *description) { prev->next = current->next; free(current); return true; } prev = current; current = current->next; } return false; } int main() { // Create the list List list = createList(); // Insert some nodes insertElement(list, "test1", time(NULL) - 1000); insertElement(list, "test3", time(NULL) - 3000); insertElement(list, "test2", time(NULL) - 2000); insertElement(list, "test4", time(NULL) - 4000); insertElement(list, "test8", time(NULL) - 8000); // Print the list debugList(list); return 0; // Search the element with value 3 Element *el = malloc(sizeof(Element)); el = findElement(list, "test3"); //printAppointment(el->appointment); //printAppointment(el->next->appointment); // Delete the element with value 3 deleteElement(list, "test3"); debugList(list); // clear the list clearList(list); // print the list again; debugList(list); return 0; }
问题分析与修复步骤
1. 核心问题:局部变量地址非法引用
在insertElement函数中,new_appointment是局部栈变量,函数执行结束后会被销毁,但代码将它的地址赋值给了new_element->appointment。后续访问这个指针时,指向的是已经释放的内存,属于野指针行为,导致描述信息无法正常读取。
修复方法:动态分配Appointment内存(不修改结构体,仅调整内存分配方式):
- 修改
createAppointment返回指针类型,内部动态分配内存:
Appointment* createAppointment(char *description, time_t start) { Appointment *appointment = malloc(sizeof(Appointment)); appointment->description = description; appointment->start = start; return appointment; }
- 同步修改
insertElement中的调用代码:
void insertElement(List list, char *description, time_t start) { printf("Inserting element %s with start time %ld\n", description, start); Element *new_element = malloc(sizeof(struct Element)); Appointment *new_appointment = createAppointment(description, start); new_element->appointment = new_appointment; new_element->next = NULL; // 后续插入逻辑保持不变... }
2. debugList循环条件错误
当前循环条件while (current->appointment != NULL)会提前终止,因为链表的tail节点未初始化appointment指针(为NULL),遍历到有效节点的下一个节点即tail时会直接退出,导致最后一个有效节点可能无法打印。
正确的循环条件应判断当前节点是否为tail:
void debugList(List list) { struct Element *current = list.head->next; while (current != list.tail) { printf("%s ", current->appointment->description); current = current->next; } printf("\n"); }
3. deleteElement字符串比较错误
当前用*currentDescription == *description仅比较字符串的第一个字符,无法正确匹配完整描述,需使用strcmp函数:
_Bool deleteElement(List list, char *description) { Element *current = list.head->next; Element *prev = list.head; while (current != list.tail) { char *currentDescription = current->appointment->description; if (strcmp(currentDescription, description) == 0) { prev->next = current->next; free(current->appointment); // 释放Appointment的内存 free(current); return true; } prev = current; current = current->next; } return false; }
4. findElement逻辑错误
当前函数仅遍历第一个节点就返回NULL,无法完成全链表查找,需遍历至tail:
Element *findElement(List list, char *description) { Element *current = list.head->next; while (current != list.tail) { if (strcmp(current->appointment->description, description) == 0) { return current; } current = current->next; } printf("Returning NULL, no element found!"); return NULL; }
5. clearList内存泄漏修复
原clearList仅释放Element内存,未释放每个Element指向的Appointment内存,需补充:
void clearList(List list) { Element *temp; while (list.head->next != list.tail) { temp = list.head->next; list.head->next = temp->next; free(temp->appointment); // 释放Appointment free(temp); } }
6. main函数冗余代码调整
main中return 0之后的代码不会执行,需将这部分代码移至return 0之前:
int main() { List list = createList(); insertElement(list, "test1", time(NULL) - 1000); insertElement(list, "test3", time(NULL) - 3000); insertElement(list, "test2", time(NULL) - 2000); insertElement(list, "test4", time(NULL) - 4000); insertElement(list, "test8", time(NULL) - 8000); debugList(list); // 查找test3 Element *el = findElement(list, "test3"); if (el != NULL) { printAppointment(el->appointment); } // 删除test3 if (deleteElement(list, "test3")) { printf("Deleted test3\n"); } debugList(list); // 清空链表 clearList(list); // 再次打印链表 debugList(list); // 释放list的头尾节点内存 free(list.head); free(list.tail); return 0; }
修复后效果
编译运行后,链表会按时间顺序打印预约描述,查找、删除功能正常工作,同时避免了野指针和内存泄漏问题。
内容的提问来源于stack exchange,提问作者noah

