在 C 中初始化多个资源的函数中处理错误(清理和中止)的一些好方法是什么?
What are some good ways of handling errors (cleanup and abort) in a function that initializes multiple resources in C?
首先,如果有人可以改写问题以使其更清楚,请这样做。
C 编程中的一个常见现象是有多个资源按特定顺序 initialized/allocated。每个资源都是后续资源初始化的先决条件。如果其中一个步骤失败,则必须取消分配先前步骤中剩余的资源。理想的伪代码(利用神奇的通用 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()
我见过几种处理这个问题的方法,它们似乎都有明显的缺点,至少在人们倾向于考虑的方面是这样 "good practice"。
处理这种情况时,可读性最强且最不容易出错的代码结构方式是什么?效率是首选,但为了可读性,可以牺牲合理的效率。请列出优点和缺点。欢迎讨论多种方法的回答。
我们的目标是希望最终得到一组解决此问题的几种首选方法。
我将从我已经看到的一些方法开始,请对它们发表评论并添加其他方法。
到目前为止我见过的三种最常见的方法:
1:嵌套 if 语句(SESE 纯粹主义者没有多个 returns)。由于有一长串先决条件,这很快就会失控。 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:多个returns。这是我目前首选的方法。逻辑很容易理解,但它仍然很危险,因为清理代码必须重复,而且很容易忘记在清理部分之一中释放某些东西。
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,但这是在 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;
}
还有什么我没有提到的吗?
4: 全局变量,哇哦!!! 因为每个人都喜欢全局变量,就像他们喜欢 goto
一样。但严重的是,如果您将变量的范围限制在文件范围内(使用 static 关键字)那么它还不错。旁注:cleanup
函数 takes/returns 错误代码不变,以便整理 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++ 获取所有全局变量,将它们放入结构中,并将它们称为 "member" 变量。不知怎的,这让每个人都很开心。
诀窍是将指向结构的指针作为第一个参数传递给所有函数。 C++ 在幕后做这件事,在 C 中你必须显式地做。我调用指针 that
以避免 conflicts/confusion 和 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 );
}
首先,如果有人可以改写问题以使其更清楚,请这样做。
C 编程中的一个常见现象是有多个资源按特定顺序 initialized/allocated。每个资源都是后续资源初始化的先决条件。如果其中一个步骤失败,则必须取消分配先前步骤中剩余的资源。理想的伪代码(利用神奇的通用 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()
我见过几种处理这个问题的方法,它们似乎都有明显的缺点,至少在人们倾向于考虑的方面是这样 "good practice"。
处理这种情况时,可读性最强且最不容易出错的代码结构方式是什么?效率是首选,但为了可读性,可以牺牲合理的效率。请列出优点和缺点。欢迎讨论多种方法的回答。
我们的目标是希望最终得到一组解决此问题的几种首选方法。
我将从我已经看到的一些方法开始,请对它们发表评论并添加其他方法。
到目前为止我见过的三种最常见的方法:
1:嵌套 if 语句(SESE 纯粹主义者没有多个 returns)。由于有一长串先决条件,这很快就会失控。 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:多个returns。这是我目前首选的方法。逻辑很容易理解,但它仍然很危险,因为清理代码必须重复,而且很容易忘记在清理部分之一中释放某些东西。
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,但这是在 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;
}
还有什么我没有提到的吗?
4: 全局变量,哇哦!!! 因为每个人都喜欢全局变量,就像他们喜欢 goto
一样。但严重的是,如果您将变量的范围限制在文件范围内(使用 static 关键字)那么它还不错。旁注:cleanup
函数 takes/returns 错误代码不变,以便整理 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++ 获取所有全局变量,将它们放入结构中,并将它们称为 "member" 变量。不知怎的,这让每个人都很开心。
诀窍是将指向结构的指针作为第一个参数传递给所有函数。 C++ 在幕后做这件事,在 C 中你必须显式地做。我调用指针 that
以避免 conflicts/confusion 和 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 );
}