如果程序因错误而提前退出,那么释放动态分配内存的正确方法是什么?

时间:2018-05-22 14:47:56

标签: c cuda return free dynamic-allocation

我一直都知道,在代码中进行错误检查是很好的,并且在满足某些条件的情况下提供错误消息。考虑以下结构的程序:

int main(const int argc, const char *argv[]) {

// read two integers M and N from input file 1 provided in argv[]
// read two integers K and L from input file 2 provided in argv[]

if (M*N < 1000000) {
    allocate array A
} else {
    printf("With your input values, the matrix will be too large!");
    return 1;
}

if (K*L < 1000000) {
    allocate array B
} else {
    printf("With your input values, the matrix will be too large!");
    return 1;
}

// multiply arrays elementwise
// free memory
return 0;
}

暂时忽略可以重新组织此代码,以便在分配发生之前检查输入参数的有效性(在这个简单示例中这将是直接的解决方案)。对于那些感兴趣的人,我将在下面详细阐述我的实际问题。

如果在上面的伪代码中成功分配了A,但是B不是因为K * L超出了限制,则程序末尾的内存空闲语句没有到达,并且内存泄漏与A相关联。如果代码无法按上述方式重新组织,那么这是避免此问题的最佳方法吗?删除return 1;可能会导致其他错误。我能想到的另外两个选项是使用臭名昭着的goto语句(我不敢),或者确实包括条件中的所有免费调用;

if (K*L < 1000000) {
    allocate array B
} else {
    printf("With your input values, the matrix will be too large!");
    free(A);
    return 1;
}

,但是当处理大量动态分配的数组时,这将意味着大量的代码重复。那么:对于中间错误检查和内存处理的组合,建议采用什么程序结构?

我的实际案例: 我正在使用CUDA(对于那些不熟悉它的人)允许编写要在GPU上执行的代码。与此API关联的所有(或大多数)代码都会返回错误标志,建议在程序运行之前检查它。在我的情况下,在我的程序中的某个时刻,我在GPU上分配内存(对于那些熟悉C的人来说应该是可读的):

double *dev_I;
cudaError_t err;
err = cudaMalloc(&dev_I, N*M, sizeof(*dev_I)); // note: cudaMalloc takes a double pointer
if (err != cudaSuccess) { printf("Error allocating dev_I.\n"); return 1; }

以下是重要部分:下一步是将内存从主机复制到GPU可以访问它的位置。这看起来有点像这样:

err = cudaMemcpy(dev_I, host_I, M*N * sizeof(*host_I), cudaMemcpyHostToDevice);
if (err != cudaSuccess) { printf("Error copying dev_I"); return 1; }

这是我关心的部分。如果分配成功但内存复制失败,程序将(应该)退出,我将留下内存泄漏。因此我的问题是如何处理这个问题。

2 个答案:

答案 0 :(得分:2)

C中的常规结构化编程需要goto,如下所示:

int f() {
  void *p = alloc_resource();
  if (p == NULL) goto error_1;

  void* q = alloc_resource();
  if (q == NULL) goto error_2;

  return g(p, q);  // g takes ownership

error_2:
  dealloc_resource(p);
error_1:
  return -1;  // some error code
}

你可以看到这种模式如何推广到任意数字或所有权获得:你基本上必须保持一个清理操作列表,并在你的控制流遇到错误时跳转到相应的模式,并且所有状态都是你的到目前为止,建立起来需要解开。

通常,人们会尝试通过保持每个函数较小来避免过度复杂的函数,以便可以在没有goto的情况下以更临时的方式完成清理,但有时您需要这样的结构(例如当你撰写多个所有权时)。

(其他语言为多个出口提供语言级支持,例如在C ++中,这个逻辑由所谓的&#34;析构函数&#34;提供。它们只是以更可组合和可重用的方式影响相同的事物。包含编译器生成的所有跳转。)

答案 1 :(得分:0)

除了@Kerrek SB好的答案外,另一种方法是将所有对象分配/初始化为某种有效状态,测试所有资源的有效性,如果可以,则使用,然后免费。

关键思想是在对象的声明(例如bar *A = NULL),对象A立即被赋予有效值 - some 有效价值。

int foo(void) {
  bar *A = NULL;
  bar *B = NULL;
  int error = 0;

  if (M*N < 1000000) {
    A = allocate(M, N);
  } else {
    printf("With your input values, the matrix will be too large!");
    error = 1;
  }

  if (K*L < 1000000) {
    B = allocate(K, L);
  } else {
    printf("With your input values, the matrix will be too large!");
    error = 1;
  }

  // Was resource allocation successful?
  if (A && B) {
   // multiply arrays
   mul(A,B);
  }

  // free resources
  free(A);
  free(B);  
  return error;
}

伪代码大纲

Get resources for A
Get resources for B
Get resources for C
If (OK(A) && OK(B) && OK(C)) {
  Perform the operation(A,B,C)
}
Return resources(C)
Return resources(B)
Return resources(A)

我发现这种方法更容易测试。