linux样式指南提供了使用goto
的具体原因,这些与您的示例一致:
https://www.kernel.org/doc/Documentation/process/coding-style.rst
使用gotos的基本原理是:
- 无条件陈述更易于理解和遵循
- 嵌套减少
- 防止在进行修改时不更新单个出口点而导致错误
- 节省了编译器的工作,以优化冗余代码;)
免责声明我不应该分享我的工作。这里的示例有些虚构,请耐心接受。
这对内存管理很有用。我最近研究了动态分配内存的代码(例如char *
,函数返回的代码)。该函数查看路径并通过解析路径的令牌来确定路径是否有效:
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
...
some statements, some involving dynamically allocated memory
...
if ( check_this() ){
free(var1);
free(var2);
...
free(varN);
return 1;
}
...
some more stuff
...
if(something()){
if ( check_that() ){
free(var1);
free(var2);
...
free(varN);
return 1;
} else {
free(var1);
free(var2);
...
free(varN);
return 0;
}
}
token = strtok(NULL,delim);
}
free(var1);
free(var2);
...
free(varN);
return 1;
现在对我来说,如果您需要添加以下代码,则以下代码会更好,更易于维护varNplus1
:
int retval = 1;
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
...
some statements, some involving dynamically allocated memory
...
if ( check_this() ){
retval = 1;
goto out_free;
}
...
some more stuff
...
if(something()){
if ( check_that() ){
retval = 1;
goto out_free;
} else {
retval = 0;
goto out_free;
}
}
token = strtok(NULL,delim);
}
out_free:
free(var1);
free(var2);
...
free(varN);
return retval;
现在,代码还存在其他各种问题,即N大于10,并且该函数超过450行,并且在某些地方具有10级嵌套。
但是我提供了我的主管来重构它,我做到了,现在它是一堆都很短的函数,而且它们都具有linux风格
int function(const char * param)
{
int retval = 1;
char * var1 = fcn_that_returns_dynamically_allocated_string(param);
if( var1 == NULL ){
retval = 0;
goto out;
}
if( isValid(var1) ){
retval = some_function(var1);
goto out_free;
}
if( isGood(var1) ){
retval = 0;
goto out_free;
}
out_free:
free(var1);
out:
return retval;
}
如果考虑不带goto
s 的等效项:
int function(const char * param)
{
int retval = 1;
char * var1 = fcn_that_returns_dynamically_allocated_string(param);
if( var1 != NULL ){
if( isValid(var1) ){
retval = some_function(var1);
} else {
if( isGood(var1) ){
retval = 0;
}
}
free(var1);
} else {
retval = 0;
}
return retval;
}
对我而言,在第一种情况下,很明显,如果第一个函数返回NULL
,则我们不在这里,而在返回0
。在第二种情况下,我必须向下滚动才能看到if包含整个函数。授予第一个在样式上向我指示此名称(名称“ out
”),第二个在语法上进行指示。第一个更加明显。
另外,我非常喜欢free()
在函数末尾使用语句。部分原因是,根据我的经验,free()
函数中间的语句很难闻,并向我表明我应该创建一个子例程。在这种情况下,我var1
在函数中创建了函数,但无法free()
在子例程中创建它,但这就是为什么goto out_free
goto out样式如此实用的原因。
我认为程序员必须长大,认为这goto
是邪恶的。然后,当他们足够成熟时,他们应该浏览Linux源代码并阅读linux样式指南。
我应该补充一点,我非常一致地使用这种样式,每个函数都有一个int retval
,一个out_free
标签和一个out标签。由于风格上的一致性,提高了可读性。
奖励:打破并继续
假设您有一个while循环
char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
var1 = functionA(line,count);
var2 = functionB(line,count);
if( functionC(var1, var2){
count++
continue;
}
...
a bunch of statements
...
count++;
free(var1);
free(var2);
}
这段代码还有其他问题,但是一件事是continue语句。我想重写整个内容,但是我的任务是以较小的方式对其进行修改。以一种令我满意的方式来重构它可能需要几天的时间,但实际的更改大约需要花半天的时间。问题在于,即使continue
我们仍然需要自由var1
和自由var2
。我必须添加一个var3
,这让我想不得不镜像free()语句。
当时我是一个相对较新的实习生,但是前一段时间我一直在看linux源代码,所以我问我的主管我是否可以使用goto语句。他说可以,而我做到了:
char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
var1 = functionA(line,count);
var2 = functionB(line,count);
var3 = newFunction(line,count);
if( functionC(var1, var2){
goto next;
}
...
a bunch of statements
...
next:
count++;
free(var1);
free(var2);
}
我认为继续充其量是可以的,但对我而言,它们就像带有隐形标签的goto。休息时间也一样。我仍然希望继续或中断,除非像这里的情况那样,它迫使您在多个位置镜像修改。
我还要补充一点,goto next;
和的使用next:
对我来说并不令人满意。它们仅比镜像free()
的和count++
语句更好。
goto
几乎总是错误的,但是必须知道何时可以使用它们。
我没有讨论的一件事是错误处理,该问题已被其他答案所涵盖。
性能
可以看看strtok()的实现http://opensource.apple.com//source/Libc/Libc-167/string.subproj/strtok.c
#include <stddef.h>
#include <string.h>
char *
strtok(s, delim)
register char *s;
register const char *delim;
{
register char *spanp;
register int c, sc;
char *tok;
static char *last;
if (s == NULL && (s = last) == NULL)
return (NULL);
/*
* Skip (span) leading delimiters (s += strspn(s, delim), sort of).
*/
cont:
c = *s++;
for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
if (c == sc)
goto cont;
}
if (c == 0) { /* no non-delimiter characters */
last = NULL;
return (NULL);
}
tok = s - 1;
/*
* Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
* Note that delim must have one NUL; we stop if we see that, too.
*/
for (;;) {
c = *s++;
spanp = (char *)delim;
do {
if ((sc = *spanp++) == c) {
if (c == 0)
s = NULL;
else
s[-1] = 0;
last = s;
return (tok);
}
} while (sc != 0);
}
/* NOTREACHED */
}
如果我错了,请指正我,但我相信cont:
标签和goto cont;
语句的存在是为了提高性能(它们肯定不会使代码更具可读性)。通过执行以下操作,可以将其替换为可读代码
while( isDelim(*s++,delim));
跳过定界符。但是为了尽可能快并避免不必要的函数调用,他们是这样做的。
我读了Dijkstra的论文,发现它很深奥。
google“ dijkstra goto声明被认为是有害的”,因为我没有足够的声誉来发布两个以上的链接。
我已经看到它被引用为不使用goto的原因,并且阅读它对我对goto的使用没有任何改变。
附录:
在考虑所有有关持续和中断的问题时,我想出了一条整洁的规则。
- 如果在while循环中有一个continue,则while循环的主体应该是一个函数,而continue应该是一个return语句。
- 如果在while循环中有一个break语句,则while循环本身应该是一个函数,而break应该成为return语句。
- 如果两者都存在,则可能出了问题。
由于范围问题,这并不总是可能的,但是我发现这样做可以使我的代码推理变得容易得多。我注意到,只要while循环中断或继续,都会给我一种不好的感觉。