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;
さて、次のコードは、aを追加する必要がある場合に、はるかに優れており、保守が容易です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
。2番目のケースでは、ifに関数全体が含まれていることを確認するために、下にスクロールする必要があります。最初の1つはスタイルを示して( " out
" という名前)、2つ目はそれを構文的に示しています。最初のものはさらに明白です。
また、free()
関数の最後にステートメントを置くことを非常に好みます。それは、私の経験でfree()
は、関数の途中のステートメントが悪臭を放ち、サブルーチンを作成する必要があることを示しているからです。この場合、var1
関数で作成し、free()
サブルーチンでは作成できませんでしたが、そのためgoto out_free
、goto outスタイルが非常に実用的です。
プログラマーは、goto
「は悪だ」と信じて育てられる必要があると思います。その後、十分に成熟したら、Linuxソースコードを参照し、Linuxスタイルガイドを読む必要があります。
すべての関数にはint retval
、out_free
label、および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);
}
このコードには他にも問題がありますが、1つは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 next;
とnext:
ラベルの使用は私にとって不満足であると付け加えるべきです。それらは単にfree()
「」と「count++
ステートメント」をミラーリングするよりも優れています。
goto
はほとんどの場合間違っていますが、いつ使用するのが適切かを知る必要があります。
私が議論しなかったことの1つは、他の回答でカバーされているエラー処理です。
性能
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));
区切り文字をスキップします。しかし、可能な限り高速になり、不必要な関数呼び出しを避けるために、彼らはこのようにします。
ダイクストラの論文を読みましたが、かなり難解です。
google「dijkstra gotoステートメントは有害と見なされます」というのは、3つ以上のリンクを投稿するほどの評判がないからです。
gotoを使用しない理由として引用されているのを見てきましたが、gotoの使用に関しては、これを読んでも何も変わりません。
補遺:
継続と中断についてこれについてすべて考えながら、きちんとしたルールを思いつきました。
- whileループ内にcontinueがある場合、whileループの本体は関数であり、continueはreturnステートメントでなければなりません。
- whileループ内にbreakステートメントがある場合、whileループ自体が関数であり、breakがreturnステートメントになる必要があります。
- 両方がある場合、何かが間違っている可能性があります。
スコープの問題のために常に可能とは限りませんが、これを行うとコードについて推論するのがはるかに簡単になることがわかりました。whileループが中断または継続するたびに不快感を覚えることに気付きました。