在C中,在失败/退出点释放分配的资源的良好编码实践是什么?

ecfdbz9o  于 2023-08-03  发布在  其他
关注(0)|答案(5)|浏览(94)

我正在做一个学校的项目,老师要求我们在项目结束时释放所有资源。
我正在努力寻找一种方法来编写更具可读性和/或更少的代码来管理它,特别是考虑到这一点更加复杂,因为不同的退出点可能会释放不同的资源集。
最简单和最混乱的解决方案似乎是这样的(退出点表示为返回-1的“some_sys_call”调用):

char *a = malloc(1);
if(some_sys_call() == -1){
   free(a);
   return -1;
}
//b is needed until the end
char *b = malloc(1);
if(some_sys_call() == -1){
   free(a);
   free(b);
   return -1;
}
//c is needed until the end
char *c = malloc(1);
//a is no longer needed from here on, so it's freed right away
free(a);
if(some_sys_call() == -1){
   free(b);
   free(c);
   return -1;
}
//Exit point when there are no errors
free(b);
free(c);
return 0;

字符串
这似乎不是很吸引人,原因很明显:你需要写很多代码,尤其是当你有很多资源的时候,这会使代码变得臃肿,而且可读性很差。当然,你不能简单地编写一个宏或函数来释放所有的资源,然后在每个退出点调用它,就像这样:

#define free_all  free(a); \
                  free(b); \
                  free(c);


你可以在技术上为每个自由集定义不同的宏/函数:

#define free_1 free(a); 

#define free_2 free(a); \
               free(b);
...


这将使“实际”代码更具可读性,但仍然需要您编写许多不同的宏,在我看来也不是一个好的解决方案。
这是否被视为常见问题?这通常是如何处理的?

ctzwtxfj

ctzwtxfj1#

这是C语言中一个众所周知的做法,尽管它是否最好还存在争议

char *a = malloc(1);
char *b = NULL;
char *c = NULL;
int ret = 0;

if(some_sys_call() == -1) {
  goto fail;
}

b = malloc(1);
if(some_sys_call() == -1) {
  goto fail;
}

c = malloc(1);
if(some_sys_call() == -1) {
  goto fail;
}

goto cleanup;

fail:
  ret = -1;
cleanup:
  free(c);
  free(b);
  free(a);

return ret;

字符串
相同但更短(受proxyres/resolver_win8.c:338启发):

char *a = malloc(1);
char *b = NULL;
char *c = NULL;
int ret = 0;

if(some_sys_call() == -1) {
  goto fail;
}

b = malloc(1);
if(some_sys_call() == -1) {
  goto fail;
}

c = malloc(1);
if(some_sys_call() == -1) {
fail:
  ret = -1;
}

free(c);
free(b);
free(a);

return ret;

g0czyy6m

g0czyy6m2#

在@273K上的变体很好的答案。

// Return 0 on success
int foo() {
  // Initialize all with "failed" default values
  char *a = NULL;
  char *b = NULL;
  char *c = NULL;
  int ret = -1;

  a = malloc(1);
  if(a == NULL || some_sys_call() == -1) {
    goto cleanup;
  }

  b = malloc(1);
  if(b == NULL || some_sys_call() == -1) {
    goto cleanup;
  }

  c = malloc(1);
  if(c == NULL || some_sys_call() == -1) {
    goto cleanup;
  }

  // Assign success only if we made it this far.
  ret = 0; // Success!

  cleanup:
  free(c);
  free(b);
  free(a);
  return ret;
}

字符串

7fyelxc5

7fyelxc53#

虽然这在很大程度上属于个人观点,但您可以实现一个链表数据结构,它保存指向已分配内存的指针,并在成功分配内存时添加这些指针。然后在失败时,您只需要遍历该列表并free每个指针。
如果你使用Map,你可以从Map中删除指针,如果它们在程序执行的正常过程中被释放。

g52tjvyc

g52tjvyc4#

有几种方法可以做到这一点,但其中一种是拥有这样的函数:

void free_all(void *a, void *b, void *c )
{
    /* We check to see if the pointer is actually allocated.  You should declare
    ** each pointer in the main module like so:  char *a = NULL, *b = NULL; */
    if(a)
        free(a);
    /*... and repeat for each and every parameter pointer... */
}

字符串
然后你可以在main()中实现如下:

if(some_error)
    free_all(a,b,c);

e37o9pze

e37o9pze5#

我的一般策略是将allocate/free与usage分开,如下所示:

int f(int x) {
    X_Buf *x_buf = malloc(x * ...);
    int f_ret = f_with_buf(x, x_buf);
    free(x_buf);

    return f_ret;
}

字符串
这也适用于其他资源,如open/closefopen/fclosemmap等。f_with_buf()函数可以正常返回,也可以提前返回错误,并且永远不用担心需要free,因为它没有执行malloc
当你需要分配多个东西时,你可以把这些链接在一起,并且存在依赖关系:

int f(int x, double y) {
    X_Buf *x_buf = malloc(x * ...);
    int f_ret = f_with_x_buf(x, x_buf, y);
    free(x_buf);

    return f_ret;
}
int f_with_x_buf(x, x_buf, y) {
    int f_ret = f_x(x, x_buf);
    if (OK != f_ret) return f_ret;

    Y_Buf *y_buf = malloc(...);
    y_buf.x_buf = x_buf;

    f_ret = f_y(y, y_buf);
    free(y_buf);

    return f_ret;
}


但并不是所有的代码都有一个很好的获取-使用-发布模式。在这些情况下,我认为你能做的最好的事情就是在一个全局位置引用任何需要清理的资源,然后使用一个cleanup函数,显式调用,或者使用atexit()。具体保留什么以及如何引用它取决于应用程序。
你也可以遵循C++的实践。虽然你不能有构造函数和析构函数,但你可以有分配和获取结构体所需的所有资源的函数,还有一个释放它们并释放它的函数,例如struct Y *y_new()y_del(struct Y *y)

相关问题