web-dev-qa-db-ja.com

なぜ値を返さずに非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;
}

...コンパイルします。

156
Catskul

C99およびC++標準では、関数が値を返す必要はありません。値を返す関数で欠落しているreturnステートメントが定義されます(0main関数のみ。

理論的根拠には、すべてのコードパスが値を返すかどうかを確認するのが非常に難しく、戻り値を組み込みアセンブラまたは他のトリッキーなメソッドで設定できることが含まれています。

C++ 11 ドラフトから:

§6.6.3/2

関数の終わりから流れる[...]は、値を返す関数で未定義の動作を引き起こします。

§3.6.1/5

mainステートメントに遭遇することなく、制御がreturnの最後に到達した場合、その効果は実行の効果です

return 0;

C++ 6.6.3/2で説明されている動作は、Cでは同じではないことに注意してください。


-Wreturn-typeオプションを付けて呼び出すと、gccは警告を表示します。

-Wreturn-type関数がデフォルトでintに設定されたreturn-typeで定義されるたびに警告します。また、return-typeがvoidではない関数でreturn-valueを持たないreturnステートメント(関数本体の末尾から落ちると、値なしで返されると見なされます)、および関数で式を含むreturnステートメントについて警告しますreturn-typeは無効です。

この警告は-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++の警告であり、一部の警告はデフォルトで無視またはオフになっています。

42
John Kugelman

あなたは、なぜ値を返す関数の終わりから流れ出す(つまり、明示的なreturnなしで終了する)のはエラーではないのですか?

まず、Cでは、関数が意味のある何かを返すかどうかは、実行中のコードが実際にses戻り値の場合にのみ重要です。たぶん、ほとんどの場合それを使用するつもりはないことがわかっているときに、言語は何も返さないようにしたくなかったのかもしれません。

第二に、言語仕様では、明示的なreturnの存在について、考えられるすべての制御パスをコンパイラー作成者に検出および検証させたくはありませんでした(多くの場合、それほど難しいことではありません)。また、一部の制御パスは、非戻り関数-一般にコンパイラーに知られていない特性につながる可能性があります。このようなパスは、迷惑な誤検知の原因になる可能性があります。

また、CとC++では、この場合の動作の定義が異なることに注意してください。 C++では、値を返す値の終わりから流出する関数は常に未定義の動作です(関数の結果が呼び出し元のコードで使用されているかどうかに関係なく)。 Cでは、呼び出し元のコードが戻り値を使用しようとした場合にのみ、未定義の動作が発生します。

13
AnT

C/C++では、何かを返すと主張する関数から戻らないことは合法です。 exit(-1)の呼び出しや、それを呼び出すか例外をスローする関数など、多くのユースケースがあります。

コンパイラーは、ユーザーに要求しない場合にUBに至っても、正当なC++を拒否しません。特に、警告なしを生成するように求めています。 (GCCはまだデフォルトでいくつかをオンにしますが、追加すると、それらは古い機能に対する新しい警告ではなく、新しい機能と一致するように見えます)

デフォルトのno-arg gccを変更していくつかの警告を出すと、既存のスクリプトまたはmakeシステムの重大な変更になる可能性があります。うまく設計されたもの-Wallおよび警告を処理するか、個々の警告を切り替えます。

C++ツールチェーンを使用することを学ぶことは、C++プログラマになることを学ぶことの障壁ですが、C++ツールチェーンは通常、専門家によって作成され、専門家のために書かれています。

どのような状況でエラーが発生しませんか?戻り値の型を宣言し、何かを返さない場合、エラーのように聞こえます。

私が考えることができる1つの例外は、main()関数です。これは、returnステートメントをまったく必要としません(少なくともC++では、C標準のいずれも手元にありません)。戻りがない場合、return 0;は最後のステートメントです。

0
David Thornley

コンパイラの警告を表示する必要があるように聞こえます:

$ 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
$
0
Chris Lutz

これはレガシーコードによるものだと思います(Cはreturnステートメントを必要としなかったので、C++もそうでした)。おそらく、その「機能」に依存する巨大なコードベースがあります。しかし、少なくとも-Werror=return-type多くのコンパイラ(gccおよびclangを含む)のフラグ。

0
d.lozinski