なぜ値を返さずに非void関数の終わりから流れ出してもコンパイラエラーが発生しないのですか?
質問
何年も前に(少なくともGCCで)デフォルトでエラーが発生しないことに気付いたので、なぜだろうといつも思っていましたか?
コンパイラフラグを発行して警告を生成できることは理解していますが、常にエラーではありませんか?有効な値を返さない非void関数に対して意味があるのはなぜですか?
コメントで要求された例:
#include <stdio.h>
int stringSize()
{
}
int main()
{
char cstring[5];
printf( "the last char is: %c\n", cstring[stringSize()-1] );
return 0;
}
...コンパイルします。
解決
C99およびC ++標準では、値を返すための関数は必要ありません。値を返す関数で欠落しているreturnステートメントは、 main
関数でのみ( 0
を返すように)定義されます。
すべてのコードパスが値を返すかどうかを確認するのは非常に難しく、組み込みアセンブラーまたは他のトリッキーなメソッドで戻り値を設定できるという理論的根拠があります。
C ++ 11 ドラフトから:
&#167; 6.6.3 / 2
関数の終わりから流れる[...]は、値を返す関数で未定義の動作を引き起こします。
&#167; 3.6.1 / 5
return
ステートメントに遭遇することなくmain
の最後に制御が到達した場合、効果はreturn 0;
C ++ 6.6.3 / 2で説明されている動作はCでは同じではないことに注意してください。
gccは、-Wreturn-typeオプションを指定して呼び出すと警告を表示します。
-Wreturn-type 関数がreturn-typeで定義されている場合は常に警告する デフォルトはintです。また、すべてについて警告する 戻り値のないreturnステートメント return-typeがそうではない関数内 void(の終わりから落ちる 関数本体は戻ると見なされます 値なし)、および戻り値について 式を含むステートメント return-typeがvoidの関数。
この警告は、 -Wall によって有効になります。
好奇心として、このコードが何をするのか見てみましょう:
#include <iostream>
int foo() {
int a = 5;
int b = a + 1;
}
int main() { std::cout << foo() << std::endl; } // may print 6
このコードの形式は未定義であり、実際には呼び出し規約およびアーキテクチャ依存。特定のシステムでは、特定のコンパイラを使用すると、戻り値は最後の式評価の結果であり、そのシステムのプロセッサの eax
レジスタに保存されます。
他のヒント
gccは、デフォルトではすべてのコードパスが値を返すことをデフォルトでチェックしません。これは一般にこれができないためです。あなたが何をしているかを知っていることを前提としています。列挙を使用した一般的な例を考えてみましょう。
Color getColor(Suit suit) {
switch (suit) {
case HEARTS: case DIAMONDS: return RED;
case SPADES: case CLUBS: return BLACK;
}
// Error, no return?
}
プログラマーは、バグがない限り、このメソッドは常に色を返すことを知っています。 gccは、あなたが何をしているのかを知っていると信頼しているので、関数の最後に強制的にリターンを配置することはありません。
一方、javacは、すべてのコードパスが値を返すことを検証しようとし、すべてのコードパスが値を返すことを証明できない場合はエラーをスローします。このエラーは、Java言語仕様で義務付けられています。間違っている場合があり、不要なreturnステートメントを挿入する必要があることに注意してください。
char getChoice() {
int ch = read();
if (ch == -1 || ch == 'q') {
System.exit(0);
}
else {
return (char) ch;
}
// Cannot reach here, but still an error.
}
これは哲学的な違いです。 CおよびC ++はJavaやC#よりも寛容で信頼できる言語であるため、新しい言語のエラーの一部はC / C ++の警告であり、一部の警告はデフォルトで無視またはオフになっています。
つまり、値を返す関数の終わりから流れ出す(つまり、明示的な return
なしで終了する)のはエラーではないのですか?
まず、Cで関数が意味のあるものを返すかどうかは、実行中のコードが実際に返された値を使用する場合にのみ重要です。たぶん、ほとんどの場合それを使用するつもりはないことがわかっているとき、言語は何も返さないようにしたくなかったのかもしれません。
第二に、明らかに、言語仕様はコンパイラの作者に強制的に明示的な return
の存在についてすべての可能な制御パスを検出および検証させたくありませんでした(多くの場合、これはそれほど難しいことではありませんが行う)。また、一部の制御パスは、非リターン関数につながる可能性があります。これは、コンパイラーには一般に知られていない特性です。このようなパスは、迷惑な誤検知の原因になる可能性があります。
また、CとC ++は、この場合の動作の定義が異なることに注意してください。 C ++では、値を返す関数の終わりから流れ出す関数は常に未定義の動作です(関数の結果が呼び出し元のコードで使用されているかどうかに関係なく)。 Cでは、呼び出しコードが戻り値を使用しようとした場合にのみ、未定義の動作が発生します。
C / C ++では、何かを返すと主張する関数から戻ることはできません。 exit(-1)
の呼び出し、またはそれを呼び出すか例外をスローする関数など、多くのユースケースがあります。
コンパイラにUBを要求しても、それがUBにつながる場合でも、コンパイラは正当なC ++を拒否しません。特に、警告なしを生成するように求めています。 (GCCはまだデフォルトでいくつかをオンにしますが、追加すると、それらは古い機能に対する新しい警告ではなく、新しい機能と一致するように見えます)
いくつかの警告を出すためにデフォルトのno-arg gccを変更すると、既存のスクリプトまたはmakeシステムの重大な変更になる可能性があります。適切に設計されたものは、 -Wall
で警告を処理するか、個々の警告を切り替えます。
C ++ツールチェーンの使用を学ぶことは、C ++プログラマになることを学ぶことの障壁ですが、C ++ツールチェーンは通常、専門家によって作成され、専門家のために作成されています。
どのような状況下でエラーが発生しませんか?戻り値の型を宣言し、何も返さない場合、エラーのように聞こえます。
考えられる1つの例外は、 main()
関数です。この関数は、 return
ステートメントをまったく必要としません(少なくともC ++では;私はしません)どちらかのC標準が便利です)。リターンがない場合、 return 0;
が最後のステートメントであるかのように動作します。
コンパイラの警告を表示する必要があるように聞こえます:
$ gcc -Wall -Wextra -Werror -x c -
int main(void) { return; }
cc1: warnings being treated as errors
<stdin>: In function ‘main’:
<stdin>:1: warning: ‘return’ with no value, in function returning non-void
<stdin>:1: warning: control reaches end of non-void function
$
これはレガシーコードによるものだと思います(Cはreturnステートメントを必要としなかったため、C ++もそうでした)。おそらくその「機能」に依存する巨大なコードベースがあるでしょう。しかし、少なくとも -Werror = return-type
があります
多くのコンパイラ(gccおよびclangを含む)のフラグ。
c99では制約違反ですが、c89では制約違反ではありません。コントラスト:
c89:
3.6.6.4
return
ステートメント制約
式を含む
return
ステートメントは、 戻り値の型がvoid
である関数。
c99:
6.8.6.4
return
ステートメント制約
式を含む
return
ステートメントは、戻り値の型がvoid
である関数には表示されません。式のないreturn
ステートメントは、戻り値の型がvoid
である関数にのみ表示されます。
-std = c99
モードであっても、gccは警告をスローします(ただし、デフォルトまたはc89 / 90)。
編集してc89に追加し、機能を終了する&quot; }
に到達することは、
式なしで return
ステートメントを実行する&quot; (3.6.6.4)。ただし、c99では、動作は未定義です(6.9.1)。