在 C 中使用 goto 进行错误处理的奇怪行为
Strange behavior using goto for error handling in C
我正在使用 OP-TEE (TrustZone Secure OS) 库用 C 语言开发一个基本应用程序。我是 运行 QEMU 中的代码。这是发生奇怪行为的代码:
void prepare_rsa_operation(TEE_OperationHandle *handle, uint32_t alg, TEE_OperationMode mode, TEE_ObjectHandle key) {
TEE_Result ret = TEE_SUCCESS;
TEE_ObjectInfo key_info;
ret = TEE_GetObjectInfo1(key, &key_info);
if (ret != TEE_SUCCESS) {
EMSG("TEE_GetObjectInfo1: %#" PRIx32, ret);
goto err;
}
ret = TEE_AllocateOperation(handle, alg, mode, key_info.keySize);
if (ret != TEE_SUCCESS) {
EMSG("Failed to alloc operation handle : 0x%x", ret);
goto err;
}
DMSG("========== Operation allocated successfully. ==========");
ret = TEE_SetOperationKey(*handle, key);
if (ret != TEE_SUCCESS) {
EMSG("Failed to set key : 0x%x", ret);
goto err;
}
DMSG("========== Operation key already set. ==========");
err:
TEE_FreeOperation(handle);
return 1;
}
发生的问题:
两个成功的消息都被打印出来(用于分配的操作和键设置),但是即使达到了 err
标签:TEE_FreeOperation(handle);
应该写成 TEE_FreeOperation(*handle);
。我修复了这个问题并删除了 return
,因为我的函数 returns void
。现在,代码工作正常,但是,据我所知,只有在其中一个条件测试(if
's)失败时才应该达到 err
标签,因为 goto
命令就在里面。
我这个理解错了吗?任何人都可以解释为什么即使之前没有发生错误也会到达 err
标签?
如果您 goto
或在执行 DMSG("========== Operation key already set. ==========");
之后到达 err:
标签。这意味着无论功能是否成功,您都会得到清理。这就是开始使用 "on error goto" 模式的全部原因。
一个比 goto 更易读的替代方法是 return 出错时将清理留在外部包装函数中。
没有阻止代码通过标签的特殊逻辑。
按照惯例,goto
通常在 C 语言中用于此类错误处理,但不一定非要这样。 goto
的标签可以自由放置在函数中的任何位置。例如,您可以这样做:
void f()
{
int i;
start:
printf("i=%d\n", i);
i++;
if (i < 10) {
goto start;
}
但请不要。
如果通过正常的执行流程到达标记语句,则不会跳过它。 IOW,给定代码
if ( condition )
{
goto err;
}
err:
// error handling code
// regular code
如果 condition
的计算结果为 false,err
之后的代码仍然会被执行,因为它跟在 if
语句之后。您可以通过使用第二个标签和 goto
:
来避免它
if ( condition )
{
goto err;
}
goto normal;
err:
// error handling code
normal:
// regular code
但是找出一个 goto-less 的方法来处理这个问题会更好。
很像 switch-case 语句中的 Case 标签,当通过正常的代码流到达时,标签将直接落入下一条指令。它只是一个可以跳到的地方。在错误后进行清理时会利用此功能。例如,如果您正在 malloc 一堆东西并发生错误,您可以根据遇到错误的时间跳转到清理代码的不同部分:
int func(void) {
int ret = -1;
int *x = malloc(sizeof(*x));
if (/* some error condition */) {
goto CLEANUP1;
}
int *y = malloc(sizeof(*y));
if (/* some error condition */) {
goto CLEANUP2;
}
int *z = malloc(sizeof(*z));
if (/* some error condition */) {
goto CLEANUP3;
}
ret = 0;
/* do whatever successful operations you want here */
CLEANUP3:
free(z);
CLEANUP2:
free(y);
CLEANUP1:
free(x);
return ret;
}
因此,对于上面的代码片段,在正常无错误执行的情况下,所有 malloc
的变量在离开函数之前都会得到 free
d。如果 malloc
ing x
后出现错误,则跳转到 CLEANUP1
标签和 free
x
。如果在 malloc
ing z
之后出现错误,那么你还 malloc
了 x
和 y
,所以你跳转到 [=23] =] 标签将 free
z
然后落入另外两个 free
s.
我正在使用 OP-TEE (TrustZone Secure OS) 库用 C 语言开发一个基本应用程序。我是 运行 QEMU 中的代码。这是发生奇怪行为的代码:
void prepare_rsa_operation(TEE_OperationHandle *handle, uint32_t alg, TEE_OperationMode mode, TEE_ObjectHandle key) {
TEE_Result ret = TEE_SUCCESS;
TEE_ObjectInfo key_info;
ret = TEE_GetObjectInfo1(key, &key_info);
if (ret != TEE_SUCCESS) {
EMSG("TEE_GetObjectInfo1: %#" PRIx32, ret);
goto err;
}
ret = TEE_AllocateOperation(handle, alg, mode, key_info.keySize);
if (ret != TEE_SUCCESS) {
EMSG("Failed to alloc operation handle : 0x%x", ret);
goto err;
}
DMSG("========== Operation allocated successfully. ==========");
ret = TEE_SetOperationKey(*handle, key);
if (ret != TEE_SUCCESS) {
EMSG("Failed to set key : 0x%x", ret);
goto err;
}
DMSG("========== Operation key already set. ==========");
err:
TEE_FreeOperation(handle);
return 1;
}
发生的问题:
两个成功的消息都被打印出来(用于分配的操作和键设置),但是即使达到了 err
标签:TEE_FreeOperation(handle);
应该写成 TEE_FreeOperation(*handle);
。我修复了这个问题并删除了 return
,因为我的函数 returns void
。现在,代码工作正常,但是,据我所知,只有在其中一个条件测试(if
's)失败时才应该达到 err
标签,因为 goto
命令就在里面。
我这个理解错了吗?任何人都可以解释为什么即使之前没有发生错误也会到达 err
标签?
如果您 goto
或在执行 DMSG("========== Operation key already set. ==========");
之后到达 err:
标签。这意味着无论功能是否成功,您都会得到清理。这就是开始使用 "on error goto" 模式的全部原因。
一个比 goto 更易读的替代方法是 return 出错时将清理留在外部包装函数中。
没有阻止代码通过标签的特殊逻辑。
按照惯例,goto
通常在 C 语言中用于此类错误处理,但不一定非要这样。 goto
的标签可以自由放置在函数中的任何位置。例如,您可以这样做:
void f()
{
int i;
start:
printf("i=%d\n", i);
i++;
if (i < 10) {
goto start;
}
但请不要。
如果通过正常的执行流程到达标记语句,则不会跳过它。 IOW,给定代码
if ( condition )
{
goto err;
}
err:
// error handling code
// regular code
如果 condition
的计算结果为 false,err
之后的代码仍然会被执行,因为它跟在 if
语句之后。您可以通过使用第二个标签和 goto
:
if ( condition )
{
goto err;
}
goto normal;
err:
// error handling code
normal:
// regular code
但是找出一个 goto-less 的方法来处理这个问题会更好。
很像 switch-case 语句中的 Case 标签,当通过正常的代码流到达时,标签将直接落入下一条指令。它只是一个可以跳到的地方。在错误后进行清理时会利用此功能。例如,如果您正在 malloc 一堆东西并发生错误,您可以根据遇到错误的时间跳转到清理代码的不同部分:
int func(void) {
int ret = -1;
int *x = malloc(sizeof(*x));
if (/* some error condition */) {
goto CLEANUP1;
}
int *y = malloc(sizeof(*y));
if (/* some error condition */) {
goto CLEANUP2;
}
int *z = malloc(sizeof(*z));
if (/* some error condition */) {
goto CLEANUP3;
}
ret = 0;
/* do whatever successful operations you want here */
CLEANUP3:
free(z);
CLEANUP2:
free(y);
CLEANUP1:
free(x);
return ret;
}
因此,对于上面的代码片段,在正常无错误执行的情况下,所有 malloc
的变量在离开函数之前都会得到 free
d。如果 malloc
ing x
后出现错误,则跳转到 CLEANUP1
标签和 free
x
。如果在 malloc
ing z
之后出现错误,那么你还 malloc
了 x
和 y
,所以你跳转到 [=23] =] 标签将 free
z
然后落入另外两个 free
s.