在 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 的变量在离开函数之前都会得到 freed。如果 mallocing x 后出现错误,则跳转到 CLEANUP1 标签和 free x。如果在 mallocing z 之后出现错误,那么你还 mallocxy,所以你跳转到 [=23] =] 标签将 free z 然后落入另外两个 frees.