在初始化C中的多个资源的函数中,有哪些处理错误(清理和中止)的好方法?

首先,如果有人可以改写问题以使其更清楚,请做。

C编程中常见的情况是要按特定顺序初始化/分配多个资源。 每个资源都是后续资源初始化的先决条件。 如果其中一个步骤失败,则必须取消分配先前步骤的剩余资源。 理想的伪代码(利用神奇的通用pure-unobtainium clean_up_and_abort()函数)看起来大致如下:

err=alloc_a() if(err) clean_up_and_abort() err=alloc_b() if(err) clean_up_and_abort() err=alloc_c() if(err) clean_up_and_abort() // ... profit() 

我已经看到了几种处理这种方法的方法,所有这些方法似乎都有明显的缺点,至少在人们倾向于考虑“良好实践”方面。

在处理这种情况时,构造代码的最可读和最不容易出错的方法是什么? 效率是优选的,但为了便于阅读,可以牺牲合理的效率。 请列出优点和缺点。 欢迎讨论多种方法的答案。

我们的目标是希望最终得到一套解决这个问题的几种首选方法。

我将从我已经看过的一些方法开始, 请对它们进行评论并添加其他方法

4: 全局变量,哇哦! 因为每个人都喜欢全局变量,就像他们喜欢goto 。 但严重的是,如果将变量的范围限制为文件范围(使用static关键字),那就不是那么糟糕了。 附注: cleanup函数接收/返回错误代码不变,以便在init_function中整理代码。

 static A *a = NULL; static B *b = NULL; static C *c = NULL; uint32_t cleanup( uint32_t errcode ) { if ( c ) dealloc_c(&c); if ( b ) dealloc_b(&b); if ( a ) dealloc_a(&a); return errcode; } uint32_t init_function( void ) { if ( alloc_a(&a) != SUCCESS ) return cleanup(INIT_FAIL_A); if ( alloc_b(&b) != SUCCESS ) return cleanup(INIT_FAIL_B); if ( alloc_c(&c) != SUCCESS ) return cleanup(INIT_FAIL_C); profit(a,b,c); return INIT_SUCCESS; } 

5: 人造OOP 。 对于那些无法处理事实的人(全局变量在C程序中实际有用),您可以采用C ++方法。 C ++获取所有全局变量,将它们放入一个结构中,并将它们称为“成员”变量。 不知怎的,这让每个人都开心。

诀窍是将指向结构的指针传递给所有函数,作为第一个参数。 C ++在幕后实现这一点,在C中你必须明确地做到这一点。 我调用指针that以避免与this冲突/混淆。

 // define a class (uhm, struct) with status, a cleanup method, and other stuff as needed typedef struct stResources { char *status; A *a; B *b; C *c; void (*cleanup)(struct stResources *that); } stResources; // the cleanup method frees all resources, and frees the struct void cleanup( stResources *that ) { if ( that->c ) dealloc_c( &that->c ); if ( that->b ) dealloc_b( &that->b ); if ( that->a ) dealloc_a( &that->a ); free( that ); } // the init function returns a pointer to the struct, or NULL if the calloc fails // the status member variable indicates whether initialization succeeded, NULL is success stResources *init_function( void ) { stResources *that = calloc( 1, sizeof(stResources) ); if ( !that ) return NULL; that->cleanup = cleanup; that->status = "Item A is out to lunch"; if ( alloc_a( &that->a ) != SUCCESS ) return that; that->status = "Item B is never available when you need it"; if ( alloc_b( &that->b ) != SUCCESS ) return that; that->status = "Item C is being hogged by some other process"; if ( alloc_c( &that->c ) != SUCCESS ) return that; that->status = NULL; // NULL is success return that; } int main( void ) { // create the resources stResources *resources = init_function(); // use the resources if ( !resources ) printf( "Buy more memory already\n" ); else if ( resources->status != NULL ) printf( "Uhm yeah, so here's the deal: %s\n", resources->status ); else profit( resources->a, resources->b, resources->c ); // free the resources if ( resources ) resources->cleanup( resources ); } 

到目前为止我见过的三种最常见的方法:

1: 嵌套的if语句 (没有SESE纯粹主义者的多次返回)。 由于存在一系列先决条件,这种情况会迅速失控。 IMO,即使在简单的情况下,这是一个可读性灾难,并没有真正的优势。 我包括它因为我看到人们经常这样做。

 uint32_t init_function() { uint32_t erra, errb, errc, status; A *a; B *b; C *c; erra = alloc_a(&a); if(erra) { status = INIT_FAIL_A; } else { errb = alloc_b(&b); if(errb) { dealloc_a(&a); status = INIT_FAIL_B; } else { errc = alloc_c(); if(errc) { dealloc_b(&b); dealloc_a(&a); status = INIT_FAIL_C; } else { profit(a,b,c); status = INIT_SUCCESS; } } } // Single return. return status; } 

2: 多次退货 。 这是我现在的首选方法。 逻辑很容易理解,但它仍然很危险,因为清理代码必须重复,并且很容易忘记在其中一个清理部分中解除分配。

 uint32_t init_function() { uint32_t err; A *a; B *b; C *c; err = alloc_a(&a); if(err) { return INIT_FAIL_A; } err = alloc_b(&b); if(err) { dealloc_a(&a); return INIT_FAIL_B; } err = alloc_c(&c); if(err) { dealloc_b(&b); dealloc_a(&a); return INIT_FAIL_C; } profit(a,b,c); return INIT_SUCCESS; } 

3: GOTO 。 许多人原则上不喜欢goto,但这是在C编程中有效使用goto的标准参数之一。 优点是很难忘记清理步骤,也没有复制。

 uint32_t init_function() { uint32_t status; uint32_t err; A *a; B *b; C *c; err = alloc_a(&a); if(err) { status = INIT_FAIL_A; goto LBL_FAIL_A; } err = alloc_b(&b); if(err) { status = INIT_FAIL_B; goto LBL_FAIL_B; } err = alloc_c(&c); if(err) { status = INIT_FAIL_C; goto LBL_FAIL_C; } profit(a,b,c); status = INIT_SUCCESS; goto LBL_SUCCESS; LBL_FAIL_C: dealloc_b(&b); LBL_FAIL_B: dealloc_a(&a); LBL_FAIL_A: LBL_SUCCESS: return status; } 

还有什么我没说的吗?