使用goto语句的场景

-- TOC --

从小就被教育要避免使用goto语句,可既然不让用,C语言保留goto语句干啥!?现在的理解是,某些场景下使用goto是合适的。

goto语句又称为无条件转移语句,使用goto语句可以使CPU直接跳转到其label标注处执行程序。但是在多数的高级编程语言课程上都不提倡使用goto语句,原因是goto语句过于灵活,过度的使用可能会导致结构化设计程序的逻辑混乱以及降低代码可读性。我在刚刚走出大学参加工作时,也对goto语句畏如蛇蝎,编程时宁可让多写很多代码也要避免使用goto语句。然而在自己不断的学习和积累经验的过程中,我常常会看到很多优秀的编程人员在使用goto语句,而且大名鼎鼎的Linux内核中有大量的goto语句,并且Linus本人也推荐合理的使用goto语句。注意,这里说的是合理的使用goto会大大简化代码,并且使程序逻辑更加清晰。

恰当使用goto,不仅不会破坏代码的可读性,还能够提高代码执行效率。需要注意的是,label后面的语句,不仅仅是goto label才运行,代码自然运行到那个位置也会执行label后面的语句。goto和其对应的label必须在同一个function内!

直接跳出多重循环

因为break只能跳出一层。

不用goto的代码结构大致如下:

int found = 0;
for (int i = 0; i < n && !found; i++)
{
        for (j = 0; j < m && !found; j++)
        {
                if (a[i] == b[j])
                {
                        found = 1;
                }
        }
}
if (found)
{
        //do something
}

使用goto的代码结构如下:

int founded = 0;
for (int i = 0; i < n; i++)
{
        for (j = 0; j < m; j++)
        {
                if (a[i] == b[j])
                {
                        founded = 1;
                        goto found;
                }
        }
}
found:
if (founded)
{
        // do something
}

注意founded这个变量是必须的,想想当此变量一直为0的时候。

以上示例只是2重循环,如果是N重循环时,goto语句的威力就发挥出来了!

资源的申请和释放

代码申请多个资源(一般都是内存),但因为错误,需要将所有资源全部释放。

Error recovery is sometimes best handled with the goto statement. We normally hate to use goto, but in our opinion, this is one situation where it is useful. Careful use of goto in error situations can eliminate a great deal of complicated, highly-indented, “structured” logic. Thus, in the kernel, goto is often used as shown here to deal with errors.

int __init my_init_function(void)
{
    int err;

    /* registration takes a pointer and a name */
    err = register_this(ptr1, "skull");
    if (err) goto fail_this;
    err = register_that(ptr2, "skull");
    if (err) goto fail_that;
    err = register_those(ptr3, "skull");
    if (err) goto fail_those;

    return 0; /* success */

    fail_those: unregister_that(ptr2, "skull");
    fail_that: unregister_this(ptr1, "skull");
    fail_this: return err; /* propagate the error */
}

注意3个label的顺序,是有讲究的。

减少重复代码

下面这段代码来自busybox的init流程:

#if ENABLE_FEATURE_USE_INITTAB
    /* optional_tty:ignored_runlevel:action:command
     * Delims are not to be collapsed and need exactly 4 tokens
     */
    while (config_read(parser, token, 4, 0, "#:",
                PARSE_NORMAL & ~(PARSE_TRIM | PARSE_COLLAPSE))) {
        /* order must correspond to SYSINIT..RESTART constants */
        static const char actions[] ALIGN1 =
            "sysinit\0""wait\0""once\0""respawn\0""askfirst\0"
            "ctrlaltdel\0""shutdown\0""restart\0";
        int action;
        char *tty = token[0];

        if (!token[3]) /* less than 4 tokens */
            goto bad_entry;
        action = index_in_strings(actions, token[2]);
        if (action < 0 || !token[3][0]) /* token[3]: command */
            goto bad_entry;
        /* turn .*TTY -> /dev/TTY */
        if (tty[0]) {
            tty = concat_path_file("/dev/", skip_dev_pfx(tty));
        }
        new_init_action(1 << action, token[3], tty);
        if (tty[0])
            free(tty);
        continue;
 bad_entry:
        message(L_LOG | L_CONSOLE, "Bad inittab entry at line %d",
                parser->lineno);
    }
    config_close(parser);
#endif

bad_entry后面就一个打印,然后开始下一轮循环。如果不适用goto,这个相同的打印代码就要出现很多地方,并且后面都要跟上continue。

a label can only be part of a statement and a declaration is not a statement

如果使用goto时,编译出现这个错误信息,表示在label后面出现了变量的申明,这(依然还)是不允许的。

Prior to C99, all declarations had to precede all statements within a block, so it wouldn't have made sense to have a label on a declaration. C99 relaxed that restriction, permitting declarations and statement to be mixed within a block, but the syntax of a labeled-statement was not changed. – Keith Thompson”

修改的方式,在:后面跟一个;,表达一句statement结束,如:

label_name:;

注意术语:declaration,statement。

本文链接:https://cs.pynote.net/sf/c/202111275/

-- EOF --

-- MORE --