当前位置:首页>Linux>C语言 goto 还能用吗?为什么 Linux 内核仍然大量使用它

C语言 goto 还能用吗?为什么 Linux 内核仍然大量使用它

  • 2026-10-11 05:37:02
C语言 goto 还能用吗?为什么 Linux 内核仍然大量使用它

引言

在 C 语言中,很少有哪个关键字像 goto 一样引发如此多的争议。

几十年来,程序员一直被告知要避免使用它。很多教材甚至直接总结为一句话:

“永远不要使用 goto。”

这种观点很大程度上源于 Edsger W. Dijkstra 于 1968 年发表的著名论文 《Go To Statement Considered Harmful》。

然而,如果你去阅读 Linux Kernel 的源代码——这个世界上规模最大、最成功的软件项目之一——你很快就会发现一个令人意外的事实:

goto 几乎随处可见。

这是否意味着 Linux Kernel 无视现代软件开发实践?

当然不是。

真实情况远比这个复杂。


真正的问题从来都不是 goto 本身

Dijkstra 并没有认为所有 goto 的使用都是错误的。

他批评的是无结构的控制流(unstructured control flow),也就是人们常说的 spaghetti code(面条式代码):程序执行流程在一个函数中毫无规律地跳来跳去。

例如:

goto LabelA;...LabelA:...goto LabelC;...LabelC:...goto LabelB;

当程序可以跳转到几乎任何位置时,代码就会变得非常难以理解,也很难验证它是否正确。

现代软件工程一直反对这种写法,因为它会降低代码的可读性、可测试性以及可维护性。

真正的问题是无结构的跳转,而不是 goto 这个关键字本身。


为什么 Linux Kernel 推荐使用 goto

Linux Kernel 官方文档专门讨论过这个问题。

它并没有禁止 goto,相反,它建议将 goto 用于集中式资源清理(centralized cleanup)和错误处理(error handling)。

Coding Style 中指出,当一个函数有多个退出点,并且这些退出点都需要执行相同的清理工作时,goto 会非常有用。(Linux Kernel Documentation [1])

与其在每一个错误判断后重复写清理代码,不如统一跳转到一个清理标签。

例如:

char *buffer = kmalloc(...);if (!buffer)return -ENOMEM;if (condition)goto out_free_buffer;...out_free_buffer:    kfree(buffer);return ret;

这种写法有几个明显的优点:

  • 减少重复的清理代码

  • 保持所有错误处理路径一致

  • 后续修改代码时更加安全

  • 减少不必要的嵌套

  • 更容易维护


在 C 中,资源管理并不容易

goto 之所以至今仍然有价值,最主要的原因是 C 本身没有自动资源管理机制。

一个函数可能需要申请多种资源,例如:

  • memory

  • files

  • sockets

  • mutexes

  • locks

假设一个函数按顺序申请了这些资源:

Allocate AAllocate BAllocate CAllocate D

如果申请 D 时失败,那么程序必须按照相反的顺序释放资源:

Free CFree BFree A

如果没有统一的清理机制,那么每一条错误处理路径都不得不重复写几乎相同的资源释放代码。

随着函数越来越大,这种代码很快就会变得难以维护。


CERT C 也推荐这种模式

CERT C Secure Coding Standard 得出了相同的结论。

它的 MEM12-C 指南建议:当函数在出错时需要释放多个资源,可以使用 goto chain 来完成统一清理。(CMU SEI [2])

与其在每一个错误处理分支中重复编写清理代码,不如把清理逻辑组织成一组标签,每个标签负责释放一层已经申请的资源,并按照相反的顺序逐步完成清理,最后退出函数。

这种模式有几个优点:

  • 资源始终按照正确的顺序释放

  • 清理代码只保留一份

  • 后续增加新的资源更加容易

  • 维护成本明显更低

需要注意的是,CERT 强调,这种建议仅适用于单个函数内部的局部错误处理,并不是鼓励在整个程序中随意使用 goto。


Linux Kernel 中的真实代码

这并不仅仅是理论上的建议。

Linux Kernel 本身就有大量函数采用了多级清理标签。

历史上,copy_process() 一直都是这种设计模式最经典的例子之一。虽然不同版本的 Linux Kernel 中,清理标签的名称已经发生了一些变化,但在今天的 kernel/fork.c 以及大量驱动代码中,仍然可以看到大量采用向前跳转(forward goto)的清理链。(SEI Wiki [3])

典型的标签包括:

goto bad_fork_cleanup_mm;goto bad_fork_cleanup_fs;goto bad_fork_cleanup_io;

每一个标签只负责释放在当前阶段之前已经成功申请的资源。

虽然这个函数包含了很多 goto 语句,但整个控制流仍然是有结构的,因为所有跳转都是向前进入统一的清理区域,然后退出函数。


为什么现代 C++ 很少需要 goto

C++ 通过 RAII(Resource Acquisition Is Initialization) 基本解决了这个问题。

对象离开作用域时,会自动释放自己所持有的资源。

例如:

std::unique_ptrstd::vectorstd::stringstd::lock_guard

因此,大多数情况下都不再需要专门的清理标签。

即使发生异常(exception),对象的析构函数也会自动执行。

因此,在现代 C++ 中,很少再需要使用 goto 来完成资源管理。

语言本身已经提供了更加安全的解决方案。


什么时候应该使用 goto?

合理的使用场景包括:

  • 集中式资源清理

  • 释放资源

  • 错误处理

  • 从多层初始化逻辑中快速退出

不合理的使用场景包括:

  • 用来实现循环

  • 替代 if

  • 在大型函数中向后跳转

  • 写出 spaghetti code

  • 在互不相关的代码之间随意跳转

有一个简单的判断原则:

如果所有 goto 都是为了跳转到同一个清理区域,那么这种写法通常是可以接受的。

如果程序执行流程在函数中到处跳来跳去,那么很可能就是代码设计出了问题。


总结

goto 本身既不是好的,也不是坏的。

1968 年那篇著名论文批评的是混乱、无结构的程序,而不是规范的错误处理方式。

如今,Linux Kernel 作为世界上最大的 C 项目之一,仍然大量使用 goto,因为它解决了一个非常现实的工程问题:在没有自动资源管理机制的语言中,实现统一的资源清理。(Linux Kernel Documentation [4])

现代 C++ 已经通过 RAII、智能指针以及确定性的析构机制,在很大程度上取代了这种模式。

但对于现代 C 来说,尤其是在系统编程、嵌入式开发以及操作系统内核中,goto 仍然是一个重要且实用的工具。

和任何语言特性一样,它真正的价值,并不取决于关键字本身,而在于你为什么使用它,以及如何使用它。

引用链接

[1] Linux Kernel Documentation: https://www.kernel.org/doc/html/latest/process/coding-style.html "Linux kernel coding style — The Linux Kernel documentation"[2] CMU SEI: https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/recommendations/memory-management-mem/mem12-c/ "MEM12-C. Consider using a goto chain when leaving a function on error when using and releasing resources | CERT Secure Coding"[3] SEI Wiki: https://wiki.sei.cmu.edu/confluence/x/odYxBQ "MEM12-C. Consider using a goto chain when leaving a function on error when using and releasing resources - SEI CERT C Coding Standard - Confluence"[4] Linux Kernel Documentation: https://www.kernel.org/doc/html/latest/process/coding-style.html "Linux kernel coding style — The Linux Kernel documentation"

最新文章

随机文章