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

时间:2011-11-12 03:45:38

标签: c

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

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

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;
}

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

我的问题在下面

哪种练习更好,如果我应该在funct2中释放内存(在我通过之后)或在funct1中(在我获得funct1的返回值之后) 第二件事是,这是否正确产生goto错误,并且错误:

if(ptr2struct) free(ptr2struct); 

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

5 个答案:

答案 0 :(得分:12)

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

答案 1 :(得分:11)

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

我尝试在与mallocing相同的功能中进行自由。这样可以将内存管理问题保存在一个地方,并且可以更好地分离关注点,因为在这种情况下,被调用的函数也可以使用未被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。它没有受到伤害(因为它的旧值在被释放之后不应该被使用)并且它具有释放空指针的良好属性被指定为无操作。

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

答案 2 :(得分:1)

  

我的问题在下面

     

哪种练习更好,如果我应该在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,即不执行任何操作。这也可以帮助您避免任何双重删除问题。

答案 3 :(得分:1)

我要分享的是我自己在C中的开发实践。它们绝不是组织自己的唯一方式。我只是概述了 方式,而不是 方式。

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

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

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

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

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

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

  • 如果我发现结构非常像一个对象,那么我选择通过不透明的API操纵结构元素STRICTLY;然后我通过每个元素的set / get类型函数定义它的接口,我在程序的每个其他部分使用的头文件中创建一个'forward声明',为结构的指针创建一个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 <foo.h>

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并不重要。希望这些代码能够使自己产生更严格的结构,而不太可能出现愚蠢的错误。

希望这有帮助。

答案 4 :(得分:0)

我知道这已经得到了解答,但我想提供我的意见。据我所知,当你用这里的参数(指针)调用一个函数时,参数被推送到堆栈(FILO)

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