这个问题倾向于事物的“设计”方面,但我想知道其他人可能如何处理这个问题,并且考虑到当前的 C 编程趋势,现在可能会这样做。
所以 OpenSSL 喜欢提供
int
如果它的功能有很多回报;成功时返回 1,失败时返回 0。为了在我的一个项目中保持错误处理的一致性,我或多或少地在我的所有函数中复制了这种返回样式。这会导致子例程如下所示:
int foo (/*...*/) {
int r;
r = some_openssl_fn(/*...*/);
if (!r) { handle_openssl_error(/*...*/); return 0; }
/* ... */
r = my_fn(/*...*/);
if (!r) { handle_regular_error(/*...*/); return 0; }
return 1;
}
现在如果我不需要使用指针就可以了。然而,我经常需要使用 BIGNUM、BN_CTX、EC_POINT 等。这些都需要指针和堆分配内存,这就是我的困境。考虑以下几点:
int bar (/*...*/) {
int r;
BIGNUM *bn;
BN_CTX *ctx;
ctx = BN_CTX_new();
if (!ctx) { handle_openssl_error(/*...*/); return 0;}
bn = BN_new();
if (!bn) { handle_openssl_error(/*...*/); return 0;}
r = BN_set_word(bn, 2ULL);
if (!r) { handle_openssl_error(/*...*/); return 0; }
r = BN_mul(bn, bn, bn, ctx);
if (!r) { handle_openssl_error(/*...*/); return 0; }
BN_free(bn);
BN_CTX_free(ctx);
return 1;
}
此代码现在的方式是,在任何 OpenSSL 函数中出错时,我将无法释放
bn
或 ctx
,该函数将返回,并且我将丢失可用于释放它们中的任何一个的任何引用。所以最初我在返回之前开始释放,如下所示:
int bar (/*...*/) {
int r;
BIGNUM *bn;
BN_CTX *ctx;
ctx = BN_CTX_new();
if (!ctx) { handle_openssl_error(/*...*/); return 0;}
bn = BN_new();
if (!bn) {
BN_CTX_free(ctx);
handle_openssl_error(/*...*/);
return 0;
}
r = BN_set_word(bn, 2ULL);
if (!r) {
BN_free(bn);
BN_CTX_free(ctx);
handle_openssl_error(/*...*/);
return 0;
}
r = BN_mul(bn, bn, bn, ctx);
if (!r) {
BN_free(bn);
BN_CTX_free(ctx);
handle_openssl_error(/*...*/);
return 0;
}
BN_free(bn);
BN_CTX_free(ctx);
return 1;
}
但正如您所看到的,它需要这种笨拙的级联冗余
free
语句,这对于读取、写入和扩展到甚至超过几个产生错误的函数或堆分配来说都是可怕的。为了解决这个问题,我看到很多人使用 goto
语句跳转到代码的释放部分,尽管 goto
似乎受到了不好的批评,但我的代码变成了以下内容:
int bar (/*...*/) {
int r;
BIGNUM *bn;
BN_CTX *ctx;
ctx = BN_CTX_new();
if (!ctx) { handle_openssl_error(/*...*/); goto err; }
bn = BN_new();
if (!bn) { handle_openssl_error(/*...*/); goto err; }
r = BN_set_word(bn, 2ULL);
if (!r) { handle_openssl_error(/*...*/); goto err; }
r = BN_mul(bn, bn, bn, ctx);
if (!r) { handle_openssl_error(/*...*/); goto err; }
BN_free(bn);
BN_CTX_free(ctx);
return 1;
err:
BN_free(bn);
BN_CTX_free(ctx);
return 0;
}
这在一定程度上清理了一些东西,但保留了一些多余的
free
(尽管我承认可能有一个更聪明的地方来放置err
标签并排除对冗余free
的需要)。然而,这会导致一个问题:如果 ctx
无法初始化,我们会跳转到 err
部分,该部分在尚未分配内存的 free
上调用 bn
。这意味着我需要创建另一个标签并交换释放 bn
和 ctx
的顺序,我认为这不是一个好的做法,因为从许多 OpenSSl 示例来看,ctx
需要在任何操作之后释放。 BIGNUM(可能是为了防止双重释放?)。
所以我只剩下裤子脱下来,双手举在空中。我基本上已经接受了这样一个事实:以这种方式使用 OpenSSL 和这种错误处理方式,我将不可避免地发生内存泄漏(由于不释放内存)或丑陋的冗余代码(通过释放它)。
if
语句而不引起内存泄漏的干净方法是什么?一些注意事项:
do { if (error) break; } while (0);
可以简化代码。NULL
的指针,我们可以有一个公共/单个退出/返回点。这是重构后的代码:
int
bar( /* ... */ )
{
BIGNUM *bn = NULL;
BN_CTX *ctx = NULL;
int r = 0; // 0=err, 1=okay
do {
ctx = BN_CTX_new();
if (!ctx)
break;
bn = BN_new();
if (!bn)
break;
r = BN_set_word(bn, 2ULL);
if (!r)
break;
r = BN_mul(bn, bn, bn, ctx);
if (!r)
break;
} while (0);
if (!r)
handle_openssl_error( /* ... */ );
if (bn)
BN_free(bn);
if (ctx)
BN_CTX_free(ctx);
return r;
}