
引言在 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 面条式代码程序执行流程在一个函数中毫无规律地跳来跳去。例如gotoLabelA; ... LabelA: ... gotoLabelC; ... LabelC: ... gotoLabelB;当程序可以跳转到几乎任何位置时代码就会变得非常难以理解也很难验证它是否正确。现代软件工程一直反对这种写法因为它会降低代码的可读性、可测试性以及可维护性。真正的问题是 无结构的跳转 而不是 goto 这个关键字本身。为什么 Linux Kernel 推荐使用 gotoLinux Kernel 官方文档专门讨论过这个问题。它并没有禁止 goto 相反它建议将 goto 用于 集中式资源清理centralized cleanup和错误处理error handling 。Coding Style 中指出当一个函数有多个退出点并且这些退出点都需要执行相同的清理工作时 goto 会非常有用。 Linux Kernel Documentation 与其在每一个错误判断后重复写清理代码不如统一跳转到一个清理标签。例如char *buffer kmalloc(...); if(!buffer) return-ENOMEM; if(condition) gotoout_free_buffer; ... out_free_buffer: kfree(buffer); returnret;这种写法有几个明显的优点在 C 中资源管理并不容易goto 之所以至今仍然有价值最主要的原因是 C 本身没有自动资源管理机制。一个函数可能需要申请多种资源例如假设一个函数按顺序申请了这些资源Allocate A Allocate B Allocate C Allocate D如果申请 D 时失败那么程序必须按照相反的顺序释放资源Free C Free B Free A如果没有统一的清理机制那么每一条错误处理路径都不得不重复写几乎相同的资源释放代码。随着函数越来越大这种代码很快就会变得难以维护。CERT C 也推荐这种模式CERT C Secure Coding Standard 得出了相同的结论。它的 MEM12-C 指南建议当函数在出错时需要释放多个资源可以使用 goto chain 来完成统一清理。 CMU SEI 与其在每一个错误处理分支中重复编写清理代码不如把清理逻辑组织成一组标签每个标签负责释放一层已经申请的资源并按照相反的顺序逐步完成清理最后退出函数。这种模式有几个优点需要注意的是CERT 强调这种建议仅适用于 单个函数内部的局部错误处理 并不是鼓励在整个程序中随意使用 goto 。Linux Kernel 中的真实代码这并不仅仅是理论上的建议。Linux Kernel 本身就有大量函数采用了多级清理标签。历史上 copy_process 一直都是这种设计模式最经典的例子之一。虽然不同版本的 Linux Kernel 中清理标签的名称已经发生了一些变化但在今天的 kernel/fork.c 以及大量驱动代码中仍然可以看到大量采用向前跳转forward goto 的清理链。 SEI Wiki 典型的标签包括gotobad_fork_cleanup_mm; gotobad_fork_cleanup_fs; gotobad_fork_cleanup_io;每一个标签只负责释放在当前阶段之前已经成功申请的资源。虽然这个函数包含了很多 goto 语句但整个控制流仍然是有结构的因为所有跳转都是 向前 进入统一的清理区域然后退出函数。为什么现代 C 很少需要 gotoC 通过 RAIIResource Acquisition Is Initialization 基本解决了这个问题。对象离开作用域时会自动释放自己所持有的资源。例如std::unique_ptr std::vector std::string std::lock_guard因此大多数情况下都不再需要专门的清理标签。即使发生异常exception对象的析构函数也会自动执行。因此在现代 C 中很少再需要使用 goto 来完成资源管理。语言本身已经提供了更加安全的解决方案。什么时候应该使用 goto 合理的使用场景包括不合理的使用场景包括有一个简单的判断原则如果所有 goto 都是为了跳转到同一个清理区域那么这种写法通常是可以接受的。如果程序执行流程在函数中到处跳来跳去那么很可能就是代码设计出了问题。总结goto 本身既不是好的也不是坏的。1968 年那篇著名论文批评的是混乱、无结构的程序而不是规范的错误处理方式。如今Linux Kernel 作为世界上最大的 C 项目之一仍然大量使用 goto 因为它解决了一个非常现实的工程问题在没有自动资源管理机制的语言中实现统一的资源清理。 Linux Kernel Documentation 现代 C 已经通过 RAII、智能指针以及确定性的析构机制在很大程度上取代了这种模式。但对于现代 C 来说尤其是在系统编程、嵌入式开发以及操作系统内核中 goto 仍然是一个重要且实用的工具。和任何语言特性一样它真正的价值并不取决于关键字本身而在于你为什么使用它以及如何使用它。引用链接Linux Kernel Documentation: https://www.kernel.org/doc/html/latest/process/coding-style.html Linux kernel coding style — The Linux Kernel documentationCMU 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 CodingSEI 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 - ConfluenceLinux Kernel Documentation: https://www.kernel.org/doc/html/latest/process/coding-style.html Linux kernel coding style — The Linux Kernel documentation