goto 名声不太好,但有一个场景它干得漂亮:错误处理。C 没有异常机制,用 goto 把出错后的统一收尾集中到一处,比到处嵌套 if 干净得多。下面讲怎么用、什么时候用、有什么坑。

goto 是什么

goto 可以在函数内跳到任意一个带标签(label)的位置:

goto label;

/* 中间这些代码不会执行 */
label:
    /* goto 之后执行到这里 */

它的问题在于用多了代码会变成意大利面条,可读性崩坏。但错误处理这种多个出错点 → 同一个清理出口的写法,反而清爽。

模拟 try-catch:除零检查

用 goto 跳到一个异常处理区:

#include <stdio.h>

int main() {
    int numerator = 10;
    int denominator = 0;
    int result;

    /* 除零就跳到异常区 */
    if (denominator == 0) {
        goto excep;
    }

    result = numerator / denominator;
    printf("%d\n", result);

excep:
    printf("Exception: Division by zero is not allowed!\n");
    return 0;
}

输出:

Exception: Division by zero is not allowed!

错误处理的标准姿势:单一清理出口

文件处理最容易出事,最怕开了一半、读了一半出错,资源没人管。用 goto 把清理集中到一个 error 标签:

#include <stdio.h>

int main() {
    FILE *file = NULL;
    int result = 0;

    file = fopen("example.txt", "r");
    if (file == NULL) {
        printf("Error opening file\n");
        goto error;   /* 出错,跳到统一清理 */
    }

    result = fread(NULL, 1, 100, file);
    if (result == 0) {
        printf("Error reading file\n");
        goto error;   /* 再出错,还是跳到这 */
    }

    printf("Successful\n");

error:
    /* 统一清理出口:文件开过就关 */
    if (file != NULL) {
        fclose(file);
    }
    return 0;
}

这样不管在哪一步出错,资源都在 error 标签那里被统一处理,不会漏。对比不用 goto 的写法,每个错误分支都要重复写 fclose,容易漏还难维护。

goto 的其他合理用途

  • 清理分配过的资源:多段 malloc 后中途出错,跳到统一 free 出口。
  • 跳出多层嵌套循环:break 只能跳一层,goto 能直接到最外层后面。
  • 单个退出路径:大函数里所有 return 换成 goto 收尾,逻辑更集中。

别滥用

  • 用多了代码绕来绕去,别人(包括自己)很难跟上。
  • 跳来跳去容易藏着 bug,尤其和变量生命周期纠缠时。
  • 更正规的替代方案:返回错误码逐层上报,或用 setjmp/longjmp 做非本地跳转。

goto 别当日常跳跃工具,只把它用在错误 → 统一清理出口这个模式上,利远大于弊。