0

我目前正在我的学校学习程序编程课程。我们使用 C 和 C99 标准。我和我的导师讨论过这个问题,我不明白为什么realloc()他的机器可以工作,但我的机器不行。

该程序的目标是解析students.txt具有学生姓名和 GPA 格式的文本文件,格式如下:

Mary 4.0
Jack 2.45
John 3.9
Jane 3.8
Mike 3.125

我有一个函数可以调整动态分配的数组的大小,当我在 CLion IDE 中使用 realloc 调试器时,它给了我 SIGABRT。

我尝试使用在线编译器并得到realloc(): invalid next size.

我整个周末都在尝试调试这个,我找不到答案,我需要帮助。

我的代码目前看起来像这样

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

#define INITIAL_SIZE 4
#define BUFFER_SIZE 512
#define GRADE_CUTOFF 3.9

// ERROR CODES
#define FILE_OPEN_ERROR 1
#define MEMORY_ALLOCATION_ERROR 2

struct student {
    double gpa;
    char *name;
};

struct student *resizeAllocationIfNeeded(struct student *listOfStudents,
        unsigned int studentCount, size_t *currentSize) {

    if (studentCount <= *currentSize) {
        return listOfStudents;
    }

    *currentSize *= 2;
    struct student *resizedList = (struct student *) realloc(listOfStudents, *currentSize * sizeof(struct student));
    if (resizedList == NULL) {
        perror("Failed to allocate memory");
        exit(MEMORY_ALLOCATION_ERROR);
    }
    return resizedList;
}

size_t getNamesAndGrades(FILE *file, struct student *listOfStudents, size_t size) {
    unsigned int studentCount = 0;
    char buffer[BUFFER_SIZE];

    while(fscanf(file, "%s %lf", buffer, &listOfStudents[studentCount].gpa) > 0) {
        listOfStudents[studentCount].name = strdup(buffer);
        studentCount++;
        listOfStudents = resizeAllocationIfNeeded(listOfStudents, studentCount, &size);
    }

    return studentCount;
}

void swapStudents(struct student *listOfStudents, int x, int y) {
    struct student temp = listOfStudents[x];
    listOfStudents[x] = listOfStudents[y];
    listOfStudents[y] = temp;
}

void sortStudentsByGPA(struct student *listOfStudents, unsigned int studentCount) {
    for (int i = 0; i < studentCount; i++) {
        for (int j = 0; j < studentCount - i - 1; j++) {
            if (listOfStudents[j].gpa < listOfStudents[j + 1].gpa) {
                swapStudents(listOfStudents, j, j + 1);
            }
        }
    }
}

void printStudentAndGPA(struct student *listOfStudents, unsigned int studentCount) {
    for (int i = 0; i < studentCount; i++) {
        if (listOfStudents[i].gpa > GRADE_CUTOFF) {
            printf("%s %lf\n", listOfStudents[i].name, listOfStudents[i].gpa);
        }
        free(listOfStudents[i].name);
    }
}

void topStudents(char *fileName) {
    FILE *file = fopen(fileName, "r");

    if (!file) {
        perror("Could not open file for reading");
        exit(FILE_OPEN_ERROR);
    }

    struct student *listOfStudents = (struct student *) malloc(INITIAL_SIZE * sizeof(struct student));

    if (listOfStudents == NULL) {
        perror("Failed to allocate memory");
        exit(MEMORY_ALLOCATION_ERROR);
    }

    unsigned int studentCount = getNamesAndGrades(file, listOfStudents, INITIAL_SIZE);
    sortStudentsByGPA(listOfStudents, studentCount);
    printStudentAndGPA(listOfStudents, studentCount);
    free(listOfStudents);
}

int main() {
    topStudents("students.txt");
    return 0;
}

4

2 回答 2

2

在检查是否需要调整数组大小时,您遇到了一个 fencepost 错误。

您的初始分配大小是4,这意味着最高有效索引是3

在循环中getNamesAndGrades(),在您读入后,您将listOfStudents[3]递增studentCount4. 然后你打电话resizeAllocationIfNeeded(listOfStudents, studentCount, &size);

里面resizeAllocationIfNeeded()studentCount == 4*currentSize == 4。所以测试

    if (studentCount <= *currentSize) {
        return listOfStudents;
    }

成功,您无需调用即可返回realloc()

然后循环的下一次迭代分配给listOfStudents[4],这会导致缓冲区溢出。

您需要将该条件更改为studentCount < *currentSize.

于 2020-11-03T03:14:47.733 回答
1

您的代码中有两个错误:一个只是拼写错误,另一个是更严重的逻辑错误。

首先,由于resizeAllocationIfNeeded(). 当 时studentCount == currentSize,这不会调整大小(即使它应该调整),这会使您溢出学生数组并导致问题。

您可以更改条件来解决此问题:

if (studentCount < *currentSize) {
    return listOfStudents;
}

除了上述之外,您的主要错误在于getNamesAndGrades(),您正在重新分配内存并将新指针分配给局部变量。然后,您可以使用该变量,topStudents()就好像它已更新一样。这当然行不通,因为传递的初始指针在第一个指针topStudents()之后变得无效,realloc()并且在返回时内存将不可撤销地丢失getNamesAndGrades()

您应该将指针传递给学生数组,或者最好让函数为您创建数组。

这是一个解决方案,重命名getNamesAndGradesgetStudents

struct student *getStudents(FILE *file, unsigned int *studentCount) {
    char buffer[BUFFER_SIZE];
    struct student *listOfStudents;
    size_t size = INITIAL_SIZE;

    *studentCount = 0;
    listOfStudents = malloc(size * sizeof(struct student));
    
    if (listOfStudents == NULL) {
        perror("Failed to allocate memory");
        exit(MEMORY_ALLOCATION_ERROR);
    }

    while(fscanf(file, "%511s %lf", buffer, &listOfStudents[*studentCount].gpa) == 2) {
        listOfStudents[*studentCount].name = strdup(buffer);
        (*studentCount)++;
        listOfStudents = resizeAllocationIfNeeded(listOfStudents, *studentCount, &size);
    }

    return listOfStudents;
}

// ...

void topStudents(char *fileName) {
    FILE *file = fopen(fileName, "r");

    if (!file) {
        perror("Could not open file for reading");
        exit(FILE_OPEN_ERROR);
    }

    unsigned int studentCount;
    struct student *listOfStudents = getStudents(file, &studentCount);

    sortStudentsByGPA(listOfStudents, studentCount);
    printStudentAndGPA(listOfStudents, studentCount);
    free(listOfStudents);
}

int main() {
    topStudents("students.txt");
    return 0;
}

补充说明:

  • 在固定大小的缓冲区(在本例中为 512 字节)上扫描时,使用%511s,而不仅仅是%s,这是等待发生的缓冲区溢出。
  • 您正在扫描两个字段,因此请检查fscanf的返回值是否为== 2> 0例如,您不希望一个字段初始化而一个字段不初始化。
  • 不要投射malloc()or的结果realloc()
  • 将来,如果您在 Linux 上,编译时gcc -g -fsanitize=address会在堆中出现问题时为您提供详细的错误报告,告诉您确切的内存分配、释放和使用位置。
于 2020-11-03T03:32:59.093 回答