在函数中释放已分配内存的结构体

4

我正在创建一个包含缓冲功能的源文件,我希望将其用于我正在创建的其他库中。

它正常工作,但是我在一个函数中创建了缓冲结构,并且无法摆脱它。以下片段应该有助于说明我的问题:

C头文件:

//dbuffer.h
...

typedef struct{
    char *pStorage;
    int *pPosition;
    int next_position;
    int number_of_strings;
    int total_size;
    }DBUFF; 
...

C语言源代码:

//dbuffer.c
...
DBUFF* dbuffer_init(char *init_pArray)
    {
    //Find out how many elements the array contains
    int size = sizeof_pArray(init_pArray);                         

    //Initialize buffer structure
    DBUFF *buffer = malloc(sizeof(DBUFF));                                       

    //Initialize the storage
    buffer->pStorage = malloc( (sizeof(char)) * (size) );

    strncpy( &(buffer->pStorage)[0] ,  &init_pArray[0] , size);
    buffer->number_of_strings = 1;

    buffer->total_size = size;
    buffer->next_position = size; //size is the next position because array allocates elements from 0 to (size-1)

    //Initialize the position tracker which keeps record of starting position for each string
    buffer->pPosition = malloc(sizeof(int) * buffer->number_of_strings );
    *(buffer->pPosition + (buffer->number_of_strings -1) ) = 0;

    return buffer;
    }

void dbuffer_destroy(DBUFF *buffer)
    {
    free(buffer->pStorage);
    free(buffer);
    }
...

主要内容:

#include <stdio.h>
#include <stdlib.h>
#include "dbuffer.h"


int main(int argc, char** argv)
    {
    DBUFF *buff; 

    buff = dbuffer_init("Bring the action");
    dbuffer_add(buff, "Bring the apostles");
    printf("BUFFER CONTENTS: ");
    dbuffer_print(buff); 

    dbuffer_destroy(buff);

    // Looks like it has been succesfully freed because output is garbage
    printf("%s\n", buff->pStorage);   

    //Why am I still able to access struct contents after the pointer has been freed ?
    printf("buff total size: %d\n", buff->total_size);

    return (EXIT_SUCCESS);
    }

输出:

BUFFER CONTENTS: Bring the action/0Bring the apostles/0
��/�
buff total size: 36

RUN SUCCESSFUL (total time: 94ms)
问题:

为什么在释放了指向结构体的指针后,我仍然可以使用下面的代码访问结构体内容?

printf("buff total size: %d\n", buff->total_size);

2
free不会使预先分配的内存不可访问。它只是将该空间释放以供下一次malloc使用。然后你仍然可以访问它,但这是UB - LPs
2
这是未定义的行为。在释放内存后,您不应访问它。 - ameyCU
1
代码遗漏了释放已分配给buffer->pPosition的内存。 - alk
@alk 谢谢,另一个用户刚刚指出了这一点,很敏锐。 - Shady Programmer
4个回答

7
一旦您调用了free()释放了分配的指针,尝试使用该指针会引发未定义行为。您不应该那样做。
引用C11标准,第§7.22.3.4章节,free()函数

free()函数使ptr指向的空间被释放,即可供进一步分配使用。[...]

它从来没有提到过清理,你可能(错误地)期望它有。
只是为了更清晰,调用free()并不总是实际上释放分配的物理内存。它只是使该指针(内存空间)能够再次分配(例如返回相同的指针)以供后续对malloc()和其它相关函数的调用。在调用free()后,该指针不应再被程序使用,但C标准并不保证已分配的内存会被清理

我知道“free”不应该清理内存。然而,我期望free会打破指针与内存内容的链接,就像我在调用“free”之后使用“printf(%s \ n,buff->pStorage);”证明的那样。但是整个buff结构体并没有被清除。我预计会留下悬空指针,但显然情况并非如此! - Shady Programmer
2
@ShadyProgrammer 你知道吗,C标准并没有强制要求使用_cleanup_、_breaking_或者将其设置为NULL。 :) - Sourav Ghosh
1
那么通过释放内存,我可能会留下指向垃圾的悬空指针或者一个表面上工作正常的指针? - Shady Programmer
2
@ShadyProgrammer,你会留下指向垃圾的悬空指针。而那个“看似工作正常”的东西,正是未定义行为的美妙之处。 :) - Sourav Ghosh
这令人非常失望,有没有办法让我证明那个表面工作正常的指针确实被释放了,并且它能够工作的原因是未定义行为而不是代码中可能存在的错误?还有:我已经正确地释放了那个指针,对吧? - Shady Programmer
2
@ShadyProgrammer 嗯,有一种广泛使用的编程风格,即在调用 free() 后立即将释放指针设置为 NULL 可能会导致在后续尝试使用指针时出现分段错误。这样,您就可以收到任何错误的提醒。 - Sourav Ghosh

6

如果尝试读取已被释放的内存,则可能会导致程序崩溃。或者也可能不会。就语言而言,这是未定义行为

编译器不会对此发出警告(或阻止您访问它)。但是显然,在调用free之后不要这样做 -

printf("buff total size: %d\n", buff->total_size);

作为良好的实践,您可以将 free 的指针设置为 NULL

我的编程方法是“证明而非承诺”,所以我决定有点冒险,看看在访问已释放的指针时是否会得到垃圾输出,但实际上并没有,这让我感到非常困惑。将已释放的指针设置为NULL是一种常见/专业的做法吗? - Shady Programmer
2
@ShadyProgrammer 把它设置为 NULL 是一种常见且良好的做法。这样,如果您访问它,通常会得到一个分段错误(比任何未知问题都更好),告诉您发生了错误。 - ameyCU
好的,所以在我的dbuffer_destroy函数内部,我按照你的建议做了以下操作: buffer->pStorage = NULL; buffer = NULL; 在这些被释放之后。但是这对我来说实际上没有任何区别。为什么? - Shady Programmer
@ShadyProgrammer,“没有区别”是什么意思?你对它有什么期望? - ameyCU
我预计程序会崩溃,或者在主函数中的 printf("buff total size: %d\n", buff->total_size); 这一行会输出垃圾值,因为现在 buff 指针的地址为 NULL。 - Shady Programmer
@ShadyProgrammer 但是也许你没有将 buff->total_size 设置为 NULL,因为你要访问它。 - ameyCU

3

free()调用只会将堆内存中的空间标记为可供使用。因此,您仍然可以使用指向该内存位置的指针,但是该内存已经不再可用。因此,下一次调用malloc()可能会将此内存分配给新的申请。

为了避免这种情况,通常在释放分配给指针的内存后,应将其设置为NULL。解引用NULL也是未定义的行为,但至少在调试时可以看到指针不应该被使用,因为它没有指向有效的内存地址。


2

为了让你的“析构函数”将传递给 NULL 的指针设置成 NULL,请按照以下方式修改代码:

void dbuffer_destroy(DBUFF ** buffer)
{
  if ((NULL == buffer) || (NULL == *buffer))
  {
     return;
  }

  free((*buffer)->pPosition);
  free((*buffer)->pStorage);
  free(*buffer);
  *buffer = NULL;
}

然后像这样调用:

  ...
  dbuffer_destroy(&buff);
  ...

你为什么把函数参数改成了双指针?看起来你做的基本上和我做的一样(但对我不起作用),只是多了一层冗余步骤? - Shady Programmer
你所谓的“冗余”实际上是必要的,以便将对 NULL 的更改反映在调用者持有的指针变量中。请注意调用函数的不同方式。@ShadyProgrammer - alk
是的,我注意到你传递的是指针的地址而不是指针所指向内容的地址,在函数内部你所做的是将指针所指向内容的地址设置为 null,这正是我一直在做的。 - Shady Programmer
1
一直在做的事情就是这个” 你是吗?在哪里?请注意,如果您在所示的代码内部执行此操作,即在 dbuffer_destroy() 中,NULL 将无法在调用函数层面上反映出来,也就是说,在 dbuffer_destroy() 返回后,buff 将不等于 NULL。C 是按值传递的。@ShadyProgrammer - alk
我已经根据您在答案中提出的建议修改了我的函数,进行了测试,并最终成功销毁了那个结构! 我向您致敬。 关键是“在dbuffer_destroy()返回后,缓冲区不会等于NULL。C按值传递。”,这是我不知道的。 您能解释一下为什么会这样吗? 因为我毕竟是在传递指针。 - Shady Programmer
当你传递一个指针时,参数变量的地址与原始指针变量相同。因此,如果您更改参数的值(设置为NULL),您并没有改变原始变量的值,您所做的唯一事情是改变参数指向的地址。 - Rafael Fontes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接