我一直都知道,在代码中进行错误检查是很好的,并且在满足某些条件的情况下提供错误消息。考虑以下结构的程序:
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; }
这是我关心的部分。如果分配成功但内存复制失败,程序将(应该)退出,我将留下内存泄漏。因此我的问题是如何处理这个问题。
答案 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)
我发现这种方法更容易测试。