在函数内部使用realloc是否正确? (以及在失败的情况下使用free())

问题描述 投票:1回答:3

我发现了一些与此类似的问题但存在一些差异。这是我的代码:student.h:

#define NUM_GRADES 5
#define NAME_LENGTH 21
#define ADDRESS_LENGTH 21
    typedef struct
{
    char name[NAME_LENGTH]; //name of a student - up to 20 chars.
    char add[ADDRESS_LENGTH]; //address - up to 20 chars.
    int grades[NUM_GRADES]; //stores the grades of a student.
}student;

//the current size of the students array.
extern int currentTotal;

//add a new student space, return 0 if failed, 1 if succeeded.
int addStudent(student **studentClass);

student.c:

    int addStudent(student **studentClass)
{
    //adds the count for the new student.
    currentTotal++; 
    //a temporary pointer to hold studentClass array in case realloc fails.
    student *temp=NULL;
    //reallocating space for the new student.
    if (!(temp = (student*)realloc(*studentClass, currentTotal * sizeof(student))))
    {
        printf("Not enough memory.\n");
        free(*studentClass);//free the original array.
        currentTotal = 0;
        return 0;
    }
    *studentClass = temp;//point class to the newly allocated space.
    printf("Added space for a student.\n");
    return 1;
}

main.c中:

#include <stdio.h>
#include <stdlib.h>
#include "student.h"
void main()
{
    student *studentClass=NULL;
....
if(addStudent(&studentClass) 
....

currentTotal是一个外部int变量。 realloc的使用是否正确?而免费使用?一旦我将指针的地址发送到另一个函数,我总是混淆我是否应该在函数内部使用*或**。 (即有一个类似* studentClass的指针,然后将&studentClass发送到另一个函数)。

如果这确实是正确的,那么* studentClass在“* studentClass = temp;”行之前指向的原始数据会发生什么? (在student.c中)?它需要被释放吗?

编辑:请不要混淆,最初* studentClass是NULL,它只是在开始时,addStudent()意味着在循环中调用,所以在第一次之后,* studentClass不再是NULL。 addStudent()在每次调用后增加* studentClass的大小。

谢谢。

c
3个回答
1
投票

它是“保存”,因为它不会引入未定义的行为,也不会泄漏内存。你要注意,如果realloc失败,原始数据不会被释放(所以你这样做),并且你将realloc的结果存储在临时变量中,以免丢失指向原始数据的指针。到目前为止一切都还可以。

然而,如果addStudent失败,它包含realloc调用者的陷阱。在这种情况下,您释放原始内存块而不提供新内存块,但不要将指针重置为NULL。所以传递给addStudent的变量仍然指向一些内存,但是这个内存已被释放。调用者可能会尝试第二次释放此内存(然后产生未定义的行为)。

如果realloc失败,我建议根据谁负责释放学生的阵列记忆来做两种选择中的任何一种:

一个。 addStudent负责:释放原始内存并将指针设置为NULL,这样外面没有人可以尝试两次释放内存。所以你要在*studentClass=NULL之后添加一个free

湾调用者负责:如果realloc失败,请不要释放原始内存;返回 - 正如您所做的那样 - 失败代码并让调用者完成其余的工作。所以你要删除free


0
投票

这一切都很好。由于*studentClassNULLrealloc(3)表现得像malloc(3)。在realloc(3)失败的情况下,它不会修改传递的指针,这意味着*studentClass仍然是NULL。在free(3)指针上调用NULL是完全有效的,但不会发生任何事情(无操作)。然后函数返回,*studentClass仍然是NULL。你只需要在调用addStudent之后检查它的值:如果它是NULL,学生的添加失败,否则它成功了。

你使用双指针也是有效的。推理这种情况的一种方法是这样的。指针允许修改指向位置的数据。如果想要将整数传递给要修改的函数,可以将其作为int*传递。在函数内部,一个取消引用它来修改它,例如*my_int = 0;。因此,如果想要在函数内修改student*,它必须作为student**传递,然后每当想要更改其内容时解除引用。


0
投票

除了Stephan Lechner提到的事实之外,代码应该单独保留分配或者将指针置空(而不是释放内存而不使指针无效),我还会看到一些其他问题:

  1. 如果不希望调用者从内存不足状态恢复,则分配函数在发生时不应返回。相反,他们应该发出故障信号(可能是通过提升信号)然后退出(如果发出故障信号并没有强制退出)。具有保证永远不会返回失败的功能可以大大简化客户端代码。
  2. 如果函数将使用全局变量来保持分配的大小,它应该使用指向分配的全局指针。如果它将使用由传入地址标识的对象指针,则它应使用也由传入地址标识的对象计数。

我可以看到解决第二个问题的四种好方法:

  1. 像现在一样传入双间接指针,但也传递指向包含学生数的整数类型对象的指针。
  2. 传入一个指向结构的指针,该结构包含学生指针和学生数量。
  3. 定义一个包含学生类型的计数和灵活数组成员的结构,并保留指向该结构而不是第一个学生的指针。
  4. 如上所述,但保留指向第一个学生的指针(而不是分配区域的开始)。这将要求调整使用malloc / realloc / free的任何代码以抵消与这些函数交换的指针。

如果学生数量增长而不是缩小,方法#3和#4可能会略有优势。如果分配区域的大小缩小为零,则无法确定对realloc区域大小为零字节的请求是否成功(如果该区域先前具有零大小,则标准将允许realloc(prevPtr,0)在具有零之后返回null成功发布了之前的分配,但标准也允许realloc失败(并返回null)而不释放之前的分配。