You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言致命堆损坏错误求助:退出菜单触发deleteList相关问题

堆损坏错误排查与修复

问题描述

选择菜单选项4(退出)时触发堆损坏错误:

HEAP CORRUPTION DETECTED: after Normal block (#76) at 0x00845EE8.

问题与deleteList函数相关,以下是完整代码:

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

#define PRO_OP 1
#define CON_OP 2
#define PRINT_OP 3
#define EXIT_OP 4

#define STR_LEN 50
#define MAX_LIST_LENGTH 10

typedef struct reasonList
{
    char* listName;
    char* reasons[MAX_LIST_LENGTH];
    int numReasons;
} reasonList;


void initList(reasonList* list, char* name);
void addReason(reasonList* list);
void printList(reasonList list);
int menu(void);
void myFgets(char str[], int n);
void deleteList(reasonList* list);

int main(void)
{
    char dilemma[STR_LEN] = { 0 };
    int op = 0;

    reasonList proList;
    initList(&proList, "PRO");
    reasonList conList;
    initList(&conList, "CON");

    printf("What is your dilemma?\n");
    myFgets(dilemma, STR_LEN);

    while (op != EXIT_OP)
    {
        op = menu();

        switch (op)
        {
        case(PRO_OP):
            addReason(&proList);
            break;
        case(CON_OP):
            addReason(&conList);
            break;
        case(PRINT_OP):
            printf("Your dilemma:\n");
            printf("%s\n\n", dilemma);

            printList(proList);
            printList(conList);
            break;
        case(EXIT_OP):
            deleteList(&proList);
            deleteList(&conList);
            break;
        }
    }
    printf("Good luck!\n");
    getchar();
    return 0;
}

/*
Function will initialize a reason list
input: the list to init, and its name
output: none
*/

void initList(reasonList* list, char* listName)
{
    list->listName = (char*) malloc(sizeof(char));
    strcpy(list->listName, listName);//equal to PRO or CON
    for (int i = 0; i < MAX_LIST_LENGTH; i++)
    {
        list->reasons[i] = (char*)malloc(sizeof(char) * STR_LEN);
        if (list->reasons[i] == NULL)
        {
            printf("no memmory left\n");
            exit(1);
        }
        strcpy(list->reasons[i], "");
    }
    list->numReasons = 0;
}

/*
Function will add a reason to the list
input: the list to add to and its name
output: none
*/
//
void addReason(reasonList* list)
{
    char* newReason = (char*)malloc(sizeof(char) * STR_LEN);
    printf("Enter a reason to add to the list %s:\n", list->listName);
    myFgets(newReason, STR_LEN);

    if (list->numReasons >= MAX_LIST_LENGTH)//if no memory left
    {
        //free(newReason);
        return;
    }
    free(list->reasons[list->numReasons]);
    list->reasons[list->numReasons] = newReason;
    list->numReasons++;
    list->reasons[list->numReasons] = NULL;
}


/*
Function will print a list of reasons
input: the list
output: none
*/
void printList(reasonList list)
{
    printf("list %s\n--------\n", list.listName);
    for (int i = 0; i < list.numReasons; i++)
    {
            printf("%s", list.reasons[i]);
        printf("\n");
    }
    printf("\n");
}

/*
Function shows menu and returns user's choice
input: none
output: user's choice
*/
int menu(void)
{
    int op = 0;
    printf("Choose option:\n");
    printf("%d - Add PRO reason\n", PRO_OP);
    printf("%d - Add CON reason\n", CON_OP);
    printf("%d - Print reasons\n", PRINT_OP);
    printf("%d - Exit\n", EXIT_OP);
    scanf("%d", &op);
    while (op < PRO_OP || op > EXIT_OP)
    {
        printf("Invalid option. Try again: ");
        scanf("%d", &op);
    }
    getchar(); // clean buffer
    return op;
}

/*
Function will delete a list
input: the list to delete
output: none
*/
void deleteList(reasonList* list)
{
    for (int i = 0; i < list->numReasons; i++)
    {
        free(list->reasons[i]);
        list->reasons[i] = NULL; // set pointer to NULL
    }
    free(list->listName);
    list->listName = NULL; // set pointer to NULL
}


/*
Function will perform the fgets command and also remove the newline
that might be at the end of the string - a known issue with fgets.
input: the buffer to read into, the number of chars to read
*/
void myFgets(char str[], int n)
{
    fgets(str, n, stdin);
    str[strcspn(str, "\n")] = 0;
}

错误原因分析

  • listName内存分配不足:initList中仅给listName分配1字节内存,但"PRO"/"CON"含终止符共需3字节,strcpy越界写入会直接破坏堆结构。
  • addReason数组越界访问:当numReasons达到MAX_LIST_LENGTH-1时,执行list->numReasons++后数值变为MAX_LIST_LENGTH,此时list->reasons[list->numReasons] = NULL;会访问超出数组下标范围的元素(数组最大下标为MAX_LIST_LENGTH-1),引发堆损坏。
  • 内存泄漏隐患:当列表已满时,addReason中分配的newReason未释放,造成内存泄漏。

修复方案

  1. 修正listName内存分配:根据传入字符串的实际长度+1分配内存,确保能容纳终止符。
  2. 移除越界赋值操作:删除addReason中的list->reasons[list->numReasons] = NULL;,numReasons已足够跟踪有效元素数量,无需修改越界位置。
  3. 处理满列表时的内存泄漏:列表已满时,先释放newReason再返回。
  4. 优化deleteList逻辑:释放所有初始化时分配的reasons元素内存(包括未使用的),避免内存泄漏。

修复后的完整代码

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

#define PRO_OP 1
#define CON_OP 2
#define PRINT_OP 3
#define EXIT_OP 4

#define STR_LEN 50
#define MAX_LIST_LENGTH 10

typedef struct reasonList
{
    char* listName;
    char* reasons[MAX_LIST_LENGTH];
    int numReasons;
} reasonList;


void initList(reasonList* list, char* name);
void addReason(reasonList* list);
void printList(reasonList list);
int menu(void);
void myFgets(char str[], int n);
void deleteList(reasonList* list);

int main(void)
{
    char dilemma[STR_LEN] = { 0 };
    int op = 0;

    reasonList proList;
    initList(&proList, "PRO");
    reasonList conList;
    initList(&conList, "CON");

    printf("What is your dilemma?\n");
    myFgets(dilemma, STR_LEN);

    while (op != EXIT_OP)
    {
        op = menu();

        switch (op)
        {
        case(PRO_OP):
            addReason(&proList);
            break;
        case(CON_OP):
            addReason(&conList);
            break;
        case(PRINT_OP):
            printf("Your dilemma:\n");
            printf("%s\n\n", dilemma);

            printList(proList);
            printList(conList);
            break;
        case(EXIT_OP):
            deleteList(&proList);
            deleteList(&conList);
            break;
        }
    }
    printf("Good luck!\n");
    getchar();
    return 0;
}

void initList(reasonList* list, char* listName)
{
    // 根据字符串长度+1分配内存,确保容纳终止符
    list->listName = (char*)malloc(strlen(listName) + 1);
    if (list->listName == NULL)
    {
        printf("no memory left\n");
        exit(1);
    }
    strcpy(list->listName, listName);
    for (int i = 0; i < MAX_LIST_LENGTH; i++)
    {
        list->reasons[i] = (char*)malloc(sizeof(char) * STR_LEN);
        if (list->reasons[i] == NULL)
        {
            printf("no memory left\n");
            exit(1);
        }
        strcpy(list->reasons[i], "");
    }
    list->numReasons = 0;
}

void addReason(reasonList* list)
{
    char* newReason = (char*)malloc(sizeof(char) * STR_LEN);
    if (newReason == NULL)
    {
        printf("no memory left\n");
        exit(1);
    }
    printf("Enter a reason to add to the list %s:\n", list->listName);
    myFgets(newReason, STR_LEN);

    if (list->numReasons >= MAX_LIST_LENGTH)
    {
        // 释放newReason避免内存泄漏
        free(newReason);
        return;
    }
    free(list->reasons[list->numReasons]);
    list->reasons[list->numReasons] = newReason;
    list->numReasons++;
}

void printList(reasonList list)
{
    printf("list %s\n--------\n", list.listName);
    for (int i = 0; i < list.numReasons; i++)
    {
        printf("%s\n", list.reasons[i]);
    }
    printf("\n");
}

int menu(void)
{
    int op = 0;
    printf("Choose option:\n");
    printf("%d - Add PRO reason\n", PRO_OP);
    printf("%d - Add CON reason\n", CON_OP);
    printf("%d - Print reasons\n", PRINT_OP);
    printf("%d - Exit\n", EXIT_OP);
    scanf("%d", &op);
    while (op < PRO_OP || op > EXIT_OP)
    {
        printf("Invalid option. Try again: ");
        scanf("%d", &op);
    }
    getchar();
    return op;
}

void deleteList(reasonList* list)
{
    // 释放所有reasons元素内存(包括未使用的)
    for (int i = 0; i < MAX_LIST_LENGTH; i++)
    {
        free(list->reasons[i]);
        list->reasons[i] = NULL;
    }
    free(list->listName);
    list->listName = NULL;
}

void myFgets(char str[], int n)
{
    fgets(str, n, stdin);
    str[strcspn(str, "\n")] = 0;
}

内容的提问来源于stack exchange,提问作者ni1

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 19:37:08