什么时候在C中释放指针以及如何知道它是否被释放

我是C的新手,试图找出C中的内存分配,我有点困惑

#include  #include  typedef struct { int a; } struct1_t; int main() { funct1(); //init pointer return 1; } int funct2(struct1_t *ptr2struct) { printf("print a is %d\n",ptr2struct->a); //free(ptr2struct); printf("value of ptr in funct2 is %p\n", ptr2struct); return 1; //success } int funct1(){ struct1_t *ptr2struct = NULL; ptr2struct = malloc(sizeof(*ptr2struct)); ptr2struct->a = 5; printf("value of ptr before used is %p", ptr2struct); if (funct2(ptr2struct) == 0) { goto error; } free(ptr2struct); printf("value of ptr in funct1 after freed is is %p\n", ptr2struct); return 1; error: if(ptr2struct) free(ptr2struct); return 0; } 

我有function1调用function2,并在使用funct1中分配的指针后,我尝试释放指针。 我创建了一个案例,如果funct2中的返回值不是1,则再次尝试释放指针。

我的问题如下

哪个练习更好,如果我应该释放funct2中的内存(在我传递之后)或者在funct1中(在我完成获取funct1的返回值之后)第二个问题是这是否正确产生goto错误,并且错误:

 if(ptr2struct) free(ptr2struct); 

我的第三个问题是,如何检查已分配的值是否已被释放? 因为在获得返回值之后,我释放指针,但是如果我打印它,它显示与分配的一个相同的位置(所以不是空指针)。

1)我应该在调用函数或被调用函数中释放它吗?

我尝试在与malloc-ing相同的function中进行自由处理。 这样可以将内存管理问题保存在一个地方,并且可以更好地分离关注点,因为在这种情况下,被调用的函数也可以使用未被malloc编辑的指针或两次使用相同的指针(如果你想这样做) )。

2)做“转到错误”是否正确?

是! 通过跳转到函数末尾的单个位置,您可以避免复制资源释放代码。 这是一种常见的模式,并没有那么糟糕,因为“goto”只是作为一种“返回”语句,并没有做任何它更为人所知的棘手和邪恶的东西。

 //in the middle of the function, whenever you would have a return statement // instead do return_value = something; goto DONE; //... DONE: //resorce management code all in one spot free(stuff); return return_value; 

另一方面,C ++有一种巧妙的方法来进行这种资源管理。 由于析构函数在函数退出之前被确定性地调用,因此它们可以用来巧妙地打包这个资源管理之王。 他们称这种技术为RAII

其他语言必须处理的另一种方式是终止块。

3)我可以看看指针是否已被释放?

可悲的是,你做不到。 有些人在释放它之后将指针变量值设置为NULL。 它没有受到伤害(因为它的旧值在被释放之后不应该被使用)并且它具有释放空指针的良好属性被指定为无操作。

但是,这样做并非万无一失。 注意让其他变量别名相同的指针,因为它们仍然包含旧值,现在是一个危险的悬空指针。

在指针上调用free()不会改变它,只将内存标记为空闲。 您的指针仍将指向包含相同值的相同位置,但该值现在可以随时被覆盖,因此在释放后不应使用指针。 为了确保这一点,最好在释放它之后始终将指针设置为NULL。

我的问题如下

哪个练习更好,如果我应该在funct2中释放内存(在我通过之后)或在funct1中(在我获得funct1的返回值之后)

这是一个“所有权”问题。 谁拥有分配的内存。 通常,这必须根据您的程序设计来决定。 例如,func1()的唯一目的可能是仅分配内存。 也就是说,在你的实现中,func1()是内存分配的函数,然后“调用”函数使用内存。 在这种情况下,释放内存的所有权是使用func1的调用者而不是使用func1()。

第二件事是,这是否是正确的goto错误和错误:使用“goto”通常是不赞成的。 它会导致代码混乱,可以轻松避免。 但是,我说“一般”。 有些情况下goto可以安静方便且有用。 例如,在大型系统中,系统配置是一大步。 现在,假设您为系统调用一个Config()函数,该函数为函数中不同点的不同数据结构分配内存,如

  config() { ...some config code... if ( a specific feature is enabled) { f1 = allocateMemory(); level = 1; } ....some more code.... if ( another feature is enabled) { f2 = allocateMemory(); level = 2; } ....some more codee.... if ( another feature is enabled) { f3 = allocateMemor(); level =3; } /*some error happens */ goto level_3; level_3: free(f3); level_2: free(f2); level_1: free(f1); } 

在这种情况下,您可以使用goto并优雅地释放分配的内存,直到配置失败。

但是,在您的示例中足以说明goto很容易避免,应该避免。

我的第三个问题是,如何检查已分配的值是否已被释放? 因为在获得返回值之后,我释放指针,但是如果我打印它,它显示与分配的一个相同的位置(所以不是空指针)。

简单。 将释放的内存设置为NULL。 除MK提到的另一个优点是,将NULL指针传递给free将导致NOP,即不执行任何操作。 这也可以帮助您避免任何双重删除问题。

我将要分享的是我自己在C中的开发实践。它们绝不是组织自己的唯一方式。 我只是简单介绍一种方式。

好吧,所以,在许多方面,“C”是一种松散的语言,所以很多纪律和严格来自于自己作为开发者。 我一直在“C”开发专业20多年,我很少需要修复我开发的任何生产级软件。 虽然相当一部分成功可能归功于经验,但其中很大一部分植根于一贯的实践。

我遵循一系列非常广泛的开发实践,并将所有内容作为命名约定的标签来处理,而不是。 我将自己限制在处理一般结构,特别是内存管理方面。

  • 如果我有一个在整个软件中使用的结构,我会写create / destroy; 初始化/完成类型函数:

      struct foo * init_foo(); void done_foo(struct foo *); 

    并在这些function中分配和取消分配结构。

  • 如果我直接在程序中操作结构元素,则不要键入它。 我很难在每个声明中使用struct关键字,以便我知道它是一个结构。 这足以让疼痛阈值不是那么多,以至于我会被它烦恼。 🙂

  • 如果我发现结构非常像一个对象,那么我选择通过不透明的API来操纵结构元素STRICTLY; 然后我通过每个元素的set / get类型函数定义它的接口,我在程序的每个其他部分使用的头文件中创建一个’forward declaration’,为结构的指针创建一个opaque typedef,并且只声明结构API实现文件中的实际结构。

foo.h中:

 struct foo; typedef struct foo foo_t; void set_e1(foo_t f, int e1); int get_ei(foo_t f); int set_buf(foo_t f, const char *buf); char * get_buf_byref(foo_t f) char * get_buf_byval(foo_t f, char *dest, size_t *dlen); 

foo.c的:

 #include  struct foo { int e1; char *buf; ... }; void set_e1(foo_t f, int e1) { f->e1 = e1; } int get_ei(foo_t f) { return f->e1; } void set_buf(foo_t f, const char *buf) { if ( f->buf ) free ( f->buf ); f->buf = strdup(buf); } char *get_buf_byref(foo_t f) { return f->buf; } char *get_buf_byval(foo_t f, char **dest, size_t *dlen) { *dlen = snprintf(*dest, (*dlen) - 1, "%s", f->buf); /* copy at most dlen-1 bytes */ return *dest; } 
  • 如果相关结构非常复杂,您甚至可能希望将函数指针直接实现到基础结构中,然后在该结构的特定扩展中提供实际操纵器。

您将看到我上面概述的方法与面向对象编程之间的强烈相似性。 这意味着……

如果你像这样保持接口干净,那么你是否必须将实例变量设置为NULL并不重要。 希望代码可以使自己适应更严格的结构,而不太可能出现愚蠢的错误。

希望这可以帮助。

我知道这已经回答了,但我想提出我的意见。 据我所知,当您使用诸如here(指针)之类的参数调用函数时,参数将被推送到堆栈(FILO)


因此,传递给函数的指针将自动弹出堆栈,但不会释放funct1()中的指针。 因此,您需要释放funct1中的指针()如果我错了,请纠正我。