If you're a programmer and you read about the recent controversy over a serious security vulnerability in Apple's Secure Transport (their SSL/TLS implementation), then you may have noticed something odd about the Apple source code where the error occurred. It's full of goto statements.
如果你是一个程序员,你读到最近的争论一个严重的安全漏洞发现在苹果的安全传输上(他们的SSL / TLS实现),那么您可能已经注意到一些奇怪的苹果源代码发生错误的地方。 它充满了goto语句。
Apple has open-sourced the Secure Transport code. We don't know whether this played a role in the bug being found, but once the fix was announced, the availability of the source code made it easy to find the specific bug.
苹果公司开源安全传输代码。 我们不知道这在发现缺陷中是否发挥了作用,但当修复被宣布,源代码的可用性导致很容易发现特定的错误。
The C language file with the error is pretty long at 1,970 lines, and it contains 47 goto statements. More primitive languages, such as old BASIC implementations, rely on goto because they lack sufficient control structures to do without them. In CS101, you are taught that goto is an ugly thing, one that breaks logical control flow. More-capable languages like C include it, if only as an escape hatch for situations where logic gets complex without it. This may be what is happening in the buggy Apple function SSLVerifySignedServerKeyExchange
:
在这个有错误的C语言文件里,有着优美的1970行代码,并且包括47个goto语句。更多的原始编程语言,例如老式的BASIC非常依赖goto语句,因为它们缺乏足够的控制结构。在CS101中,你被教导goto语句是有害的丑陋的东西,它破坏了逻辑控制流。能力更强的编程语言如C,如果在只有一个结束(退出)出口的情况下不使用goto语句将会使逻辑变得复杂。这就是苹果被发现有缺陷的函数SSLVerifySignedServerKeyExchange出现的情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
static
OSStatus SSLVerifySignedServerKeyExchange(…)
{
…
if
((err = SSLFreeBuffer(&hashCtx)) != 0)
goto
fail;
if
((err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0)
goto
fail;
if
((err = SSLHashSHA1.update(&hashCtx, &clientRandom)) != 0)
goto
fail;
if
((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
goto
fail;
if
((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
goto
fail;
goto
fail;
if
((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
goto
fail;
…
fail:
SSLFreeBuffer(&signedHashes);
SSLFreeBuffer(&hashCtx);
return
err;
}
|
I've cut a lot of code out of the function for readability. The error is where there are two consecutive lines that say goto fail;
. I wouldn't say that the use of goto in this program contributed to the error at all. Clearly, the error is a simple, careless editing mistake, one which was still syntactically correct and which therefore generated no errors. You can easily do the same without goto.
为了可读性,我已经去除了大量的代码。这个错误发生在连续两行出现了“goto fail"; 我不认为这是因为程序使用了goto语句导致了错误。很显然,这个错误是低级的,粗心的编辑性错误,语法上是正确的因此没有错误。你可能不使用goto语句也能实现这个功能。
But what's with all the gotos? Do real programmers for serious companies like Apple actually use them so frequently? It seems like they do. A search for "goto fail" on Github yields millions of results.
但是这和goto语句有什么关系?为什么像苹果这么严肃的公司这么频繁的使用goto语句?似乎像他们这样的做法,在Github里搜索“goto fail”也可以搜出数以百计的结果。
In fairness to these programmers, in C, there's little effective difference between goto and other, less taboo statements, like break
inside a switch construct. In that spirit, careful use of goto can actually make code clearer. Another common reason given for using goto as a means of hand-optimization of code is less defensible.
公平来说,站在程序员的角度,在C语言里,goto语句和其他语句(例如在switch结构里的break语句)相比,效率有没有优势,是否有什么忌讳?本着这个精神,小心使用goto语句事实上可以使代码更清晰简洁。至于另一原因:goto语句可以手动优化代码,这个不太能成立。
I asked Jeff Law and Jason Merrill, both engineers at Red Hat and members of the steering committee for GCC, what they thought of goto. Merrill quickly shoots down any idea that programmers can or should hand-optimize code for performance:
我曾经问过Jeff Law and Jason Merrill(他们都为RedHat及其成员的GCC服务过的工程师):他们是怎么考虑goto语句的?Merrill 迅速地攻击了程序员为了性能可以或应该手动优化代码的观点:
"As Donald Knuth wrote in his paper Structured Programming with Go To Statements, '…premature optimization is the root of all evil.' Programming for clarity is much more important until you know what the hot spots are in your code, especially given that modern optimizers and profiling tools are much more powerful than anything available to Knuth when he wrote the paper in 1974; most of the examples he gives of useful optimizations using goto are routine transformations for a modern compiler."
“Donald Knuth在他的论文结构化编程语句写道,”…过早的优化是万恶之源。 清晰的编程重要得多,直到你知道您的代码中的hot spot,特别是考虑到现代的优化和分析工具比Knuth当他在1974年写论文是更加强大;大部分他给出的例子:使用goto来进行优化,事实上现代编译器都已经做了这部分工作。”
Merrill agrees that goto in the Apple code appears to be used for clarity: "In general, micro-optimizing code at the local level is not something we would recommend until after verifying that the code is a hot spot and that micro-optimization would prove to be valuable. Clarity trumps micro-optimization most of the time.
Merrill同意使用goto语句使苹果代码里在简洁性上非常有用:“总的来说,在那种水平上微小的优化代码我们是不推荐的,除非你非常确定那段代码是hot spot并且这种优化被证明是非常有价值的。大多数时间,代码简洁清晰胜过这种微小优化。
GCC and, I suspect, most modern compilers internally transform the source into a series of basic blocks (maximal set of consecutive insns that is ended by a change in flow control or a label). All transfer of control between blocks is represented by the control flow graph for most of the optimization phases. What this means is that there's no difference to the compiler between well-structured code and spaghetti code created by gotos. In the end, all those changes in control flow are turned into edges in the control flow graph (CFG), and the compiler optimizes them to the best extent possible. Relatively late in the compiler, edges in the CFG are explicitly represented as control flow instructions again."
GCC和(我怀疑)绝大多数现代编译器内部都实现了对代码的转换为一系列基本指令的过程中,在控制流程里都做了优化。这就意味着,对于编译器来说,良好结构的代码和goto语句产生的面条式代码(这里没有贬义)根本没有区别。最终都会转化为相同的流程(后面一大堆就不翻译了,也不好翻译)。"
Law also agrees that Apple's use of gotos is "…likely being more about cleaning up properly than optimization. You would see similar idioms in lots of code where cleanups (particularly releasing memory as is the case in this code fragment) are needed before returning from a function. C++ has significantly better support for this kind of cleanup idiom. GCC's C compiler also supports the "cleanup" attribute, which brings similar capabilities to C code."
Law也是赞成苹果在使用goto语句上的做法:”...可以看到在清理工作中是恰当选择。你可以看到在清除工作中需要返回一个值的函数中类似的风格是需要的。C++对于这种清理工作有非常好的支持。GCC的编译器也支持这种“清除”属性,它为C代码带来了类似的功能。
So the taboo against goto isn't entirely rational. Look at the SSLVerifySignedServerKeyExchange
function — how would you structure it without goto? Would it actually be clearer? In that routine, goto is used to escape only out of linear flow logic, not loops. It's perhaps another thing to goto out of the middle of a while
or for
loop, as you may be making it more difficult to confirm the correctness of those loop structures.
因此这种禁止使用goto语句并不是完全合理的。看看 SSLVerifySignedServerKeyExchange这个函数-你该如何在不使用goto语句的情况下结构化呢?能够比它更清晰吗?没翻译完,赶车回家……
Even if it makes sense to use goto at times, the more important lesson to learn is about how much better languages have gotten in the last couple of decades. [It seems to me the lesson to learn here is to static check your code before checking it in. —Ed.] The advances in compiler optimization, paired with Moore's law, allow us to use languages that manage cleanup and compartmentalize logic far better than C. Not that Apple should rewrite Secure Transport in Python, but everyone should take the inherent dangers of C more seriously now that the performance imperative for it is not what it used to be.
Larry Seltzer is an independent writer, who previously was editor-in-chief of Byte.com.