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

Ern*_*ldo 7 c free memory-management heap-memory

我正在开展一个学校项目,老师要求我们在项目终止时释放所有资源。

我正在努力寻找一种方法来编写更具可读性和/或更少的代码来管理它,特别是考虑到不同的退出点可能会释放不同的资源集,这使得这变得更加复杂。

最简单和最混乱的解决方案似乎是这样的(退出点表示为返回 -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;
Run Code Online (Sandbox Code Playgroud)

由于显而易见的原因,这似乎不太吸引人:您需要编写大量代码,尤其是当您拥有大量资源时,这会导致代码因释放而变得臃肿且可读性较差。当然,您不能简单地编写一个宏或函数来释放所有资源并在每个退出点调用它,如下所示:

#define free_all  free(a); \
                  free(b); \
                  free(c);
Run Code Online (Sandbox Code Playgroud)

从技术上讲,您可以为每组免费定义不同的宏/函数:

#define free_1 free(a); 

#define free_2 free(a); \
               free(b);
...
Run Code Online (Sandbox Code Playgroud)

这将使“实际”代码更具可读性,但仍然需要您编写许多不同的宏,并且在我看来也不是一个好的解决方案。

这被认为是一个常见问题吗?通常如何处理这种情况?

S.M*_*.M. 11

这是 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;
Run Code Online (Sandbox Code Playgroud)

相同但更短(受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;
Run Code Online (Sandbox Code Playgroud)

  • 这是 C 代码中“goto”少数可接受的用法之一。 (6认同)

chu*_*ica 7

@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;
}
Run Code Online (Sandbox Code Playgroud)