【问题标题】:Why is realloc giving me inconsistent behaviour?为什么 realloc 给了我不一致的行为?
【发布时间】:2020-11-03 03:03:12
【问题描述】:

我目前正在学校学习过程编程课程。我们使用 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;
}

【问题讨论】:

  • 通过 valgrind 运行您的代码。如果你的内存管理不善,它会告诉你在哪里。
  • 您要么需要在添加学生之前调整数组大小,要么将resizeAllocationIfNeeded 中的if (studentCount &lt;= *currentSize) 更改为if (studentCount &lt; *currentSize)
  • 感谢您花时间查看我的代码。更改条件检查后,它仍然给我相同的行为。在此期间,我将通过 Valgrind 看看它。

标签: c realloc memory-reallocation


【解决方案1】:

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

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

getNamesAndGrades() 的循环中,读入listOfStudents[3] 后,将studentCount 递增到4。然后你打电话给resizeAllocationIfNeeded(listOfStudents, studentCount, &amp;size);

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

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

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

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

您需要将该条件更改为studentCount &lt; *currentSize

【讨论】:

  • 感谢您花时间查看我的代码。更改条件检查后,它仍然给我同样的行为。
  • 我没有看到任何其他明显的错误。我建议您按照其他人的建议使用 valgrind。
【解决方案2】:

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

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

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

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

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

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

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

【讨论】:

  • 哇,这很难发现。我什至没有想到我需要将引用传递给我的 malloc 数组这一事实,因为我在两个级别的函数调用中使用它。您的解决方案更有意义,因为代码更容易理解。非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多