为什么 realloc 给我不一致的行为?

Why is realloc giving me inconsistent behaviour?

我目前正在我的学校学习过程编程课程。我们使用符合 C99 标准的 C。我和我的导师讨论过这个问题,我不明白为什么 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;
}

检查是否需要调整数组大小时出现fencepost错误。

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

getNamesAndGrades() 的循环中,读入 listOfStudents[3] 后,将 studentCount 增加到 4。然后你调用 resizeAllocationIfNeeded(listOfStudents, studentCount, &size);

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

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

成功,您 return 无需调用 realloc()

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

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

你的代码有两个错误:一个只是笔误,另一个是比较严重的逻辑错误。

首先,由于 resizeAllocationIfNeeded() 中的条件,您重新分配为时已晚。当 studentCount == currentSize 时,这不会调整大小(即使它应该),这会使您溢出学生数组并导致问题。

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

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

除上述之外,您的主要错误在 getNamesAndGrades(),您在此处重新分配内存并将新指针分配给 局部变量 。然后,您可以在 topStudents() 中使用该变量,就像它已更新一样。这当然行不通,因为 topStudents() 传递的初始指针在第一个 realloc() 之后变得无效,并且当 getNamesAndGrades() returns.

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

这里有一个解决方案,将 getNamesAndGrades 重命名为 getStudents:

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 的 return 值是否为 == 2,而不是 > 0,例如您不需要一个字段已初始化,一个未初始化。
  • Don't cast the result of malloc() or realloc()
  • 将来,如果您在 Linux,使用 gcc -g -fsanitize=address 编译会在堆中出现问题时为您提供详细的错误报告,告诉您内存分配、释放和释放的确切位置用过。