为什么 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
编译会在堆中出现问题时为您提供详细的错误报告,告诉您内存分配、释放和释放的确切位置用过。
我目前正在我的学校学习过程编程课程。我们使用符合 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()
orrealloc()
- 将来,如果您在 Linux,使用
gcc -g -fsanitize=address
编译会在堆中出现问题时为您提供详细的错误报告,告诉您内存分配、释放和释放的确切位置用过。