C语言栈实现中realloc使用错误导致0xC0000374崩溃排查
我尝试用C语言实现一个栈,包含入栈(dodaj_do_stosu)、出栈(usun_ze_stosu)和查看栈顶的功能。程序编译后前两个元素可正常运行,但之后返回0xC0000374错误。我怀疑是realloc使用错误或结构体sizeof计算有误,请求帮忙排查代码,重点关注dodaj_do_stosu函数。我的代码如下:
#include <stdio.h> #include <stdlib.h> #include <time.h> typedef struct { int n; char *nazwa; }element_t; typedef struct { int rozmiar; element_t **tablica; }stos_t; void top_of_stack(stos_t *s){ printf("ostatni element stosu:rozmiar = %d nazwa=%s, n=%d\n", s->rozmiar, s->tablica[s->rozmiar]->nazwa, s->tablica[s->rozmiar]->n); } void init(stos_t *s) { s->rozmiar=0; s->tablica=malloc(0); } void dodaj_do_stosu(stos_t *s, int n, char *name) { s->tablica = realloc(s->tablica, (s->rozmiar + 1) * sizeof(s->tablica)); s->tablica[s->rozmiar]->nazwa = name; s->tablica[s->rozmiar]->n = n; printf("rozmiar=%d, n=%d , nazwa=%s\n",s->rozmiar, s->tablica[s->rozmiar]->n, s->tablica[s->rozmiar]->nazwa); s->rozmiar++; } void usun_ze_stosu(stos_t *s) { s->tablica = realloc(s->tablica, (s->rozmiar - 1) * sizeof(s->tablica[0])); s->rozmiar--; } void rm(stos_t s) { free(s.tablica); } int main(int argc, char **argv) { stos_t s; init(&s); int i; srand(time(0)); if (argc>1) for(i=1;i<argc;i++){ printf("%s\n", argv[i]); dodaj_do_stosu(&s, rand() % 10, argv[i]); } for(i=0;i<argc-1;i++){ //printf("i=%d, n=%d, nazwa=%s\n",i, s.tablica[i].n, s.tablica[i].nazwa); } //top_of_stack(&s); //usun_ze_stosu(&s); //top_of_stack(&s); rm(s); return 0; }
核心错误点分析
1. dodaj_do_stosu中内存分配的sizeof计算错误
你用了sizeof(s->tablica),但s->tablica是element_t**类型(指针的指针),这个sizeof得到的是指针本身的大小(比如32位系统4字节,64位8字节)。虽然数值上和element_t*的大小一致,但逻辑错误,且如果后续类型变更容易出问题。正确写法应该用sizeof(element_t*)或者sizeof(s->tablica[0]),后者更健壮。
2. 未为element_t实例分配内存
s->tablica[s->rozmiar]是未初始化的野指针,直接访问它的nazwa和n成员会触发非法内存访问。每次入栈时,必须先为新的element_t分配内存,再进行赋值操作。
3. top_of_stack的栈顶索引错误
栈的rozmiar是元素个数,索引从0开始,栈顶元素的索引应该是s->rozmiar - 1,而不是s->rozmiar,后者会访问超出数组范围的内存,直接触发越界错误。
4. init函数中malloc(0)的隐患
虽然部分编译器允许malloc(0)返回可被free的指针,但这种写法不规范,realloc对NULL的处理更可靠,建议初始化为s->tablica = NULL。
5. usun_ze_stosu的边界判断缺失
当栈为空(rozmiar == 0)时,调用该函数会让realloc的大小为-1(转换为无符号数后是极大值),直接触发内存错误,必须先判断rozmiar > 0再执行缩容。
修正后的完整代码
#include <stdio.h> #include <stdlib.h> #include <time.h> typedef struct { int n; char *nazwa; } element_t; typedef struct { int rozmiar; element_t **tablica; } stos_t; void top_of_stack(stos_t *s) { if (s->rozmiar == 0) { printf("Stos jest pusty!\n"); return; } element_t *top = s->tablica[s->rozmiar - 1]; printf("Ostatni element stosu: rozmiar = %d, nazwa=%s, n=%d\n", s->rozmiar, top->nazwa, top->n); } void init(stos_t *s) { s->rozmiar = 0; s->tablica = NULL; // 初始化为NULL,realloc处理更可靠 } void dodaj_do_stosu(stos_t *s, int n, char *name) { // 扩容:使用sizeof(s->tablica[0])确保类型正确 element_t **new_tab = realloc(s->tablica, (s->rozmiar + 1) * sizeof(s->tablica[0])); if (new_tab == NULL) { perror("realloc failed"); exit(EXIT_FAILURE); } s->tablica = new_tab; // 为新元素分配内存 s->tablica[s->rozmiar] = malloc(sizeof(element_t)); if (s->tablica[s->rozmiar] == NULL) { perror("malloc failed"); exit(EXIT_FAILURE); } // 赋值 s->tablica[s->rozmiar]->nazwa = name; s->tablica[s->rozmiar]->n = n; printf("rozmiar=%d, n=%d , nazwa=%s\n", s->rozmiar, s->tablica[s->rozmiar]->n, s->tablica[s->rozmiar]->nazwa); s->rozmiar++; } void usun_ze_stosu(stos_t *s) { if (s->rozmiar == 0) { printf("Nie mozna usunac elementu z pustego stosu!\n"); return; } // 先释放栈顶元素的内存 free(s->tablica[s->rozmiar - 1]); // 缩容栈的指针数组 element_t **new_tab = realloc(s->tablica, (s->rozmiar - 1) * sizeof(s->tablica[0])); if (new_tab != NULL || s->rozmiar - 1 == 0) { // 缩容到0时realloc返回NULL是合法的 s->tablica = new_tab; } s->rozmiar--; } void rm(stos_t *s) { // 改为传指针,避免拷贝,同时释放所有元素内存 for (int i = 0; i < s->rozmiar; i++) { free(s->tablica[i]); } free(s->tablica); s->tablica = NULL; s->rozmiar = 0; } int main(int argc, char **argv) { stos_t s; init(&s); int i; srand(time(0)); if (argc > 1) { for (i = 1; i < argc; i++) { printf("%s\n", argv[i]); dodaj_do_stosu(&s, rand() % 10, argv[i]); } } top_of_stack(&s); usun_ze_stosu(&s); top_of_stack(&s); rm(&s); return 0; }
额外优化说明
- 增加了内存分配失败的错误处理(
perror和exit),避免程序静默崩溃 rm函数改为传指针,先释放每个element_t的内存再释放指针数组,彻底避免内存泄漏- 出栈时先释放栈顶元素内存再缩容指针数组,防止内存泄漏
- 所有栈操作增加空栈判断,提升程序健壮性
内容的提问来源于stack exchange,提问作者Michał Jagodzinski

