値を返さずに非void関数の最後をオフにしても、コンパイラエラーが発生しないのはなぜですか?


158

私が何年も前に気付いて以来、これはデフォルトではエラーを生成しない(少なくともGCCでは)ことを知っているので、私はいつもその理由を疑問に思っていましたか?

コンパイラフラグを発行して警告を生成できることを理解しましたが、常にエラーである必要はありませんか?非void関数が値を返さないのが有効であることはなぜ意味があるのですか?

コメントでリクエストされた例:

#include <stdio.h>
int stringSize()
{
}

int main()
{
    char cstring[5];
    printf( "the last char is: %c\n", cstring[stringSize()-1] ); 
    return 0;
}

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


9
または、すべての警告を些細なエラーのように扱い、可能な場合はすべての警告をアクティブにします(必要に応じてローカルで非アクティブにしますが、コードではその理由が明確です)。
Matthieu M.

8
-Werror=return-typeその警告だけをエラーとして扱います。私は警告を無視しただけであり、無効なthisポインタを追跡する欲求不満の数分間が私をここに導き、この結論に導きました。
jozxyqk 2013年

これは、std::optional関数の最後を返さずに終了すると「真の」オプションが返されるという事実によってさらに悪化します
Rufus

@Rufus必要はありません。それはあなたのマシン/コンパイラ/ OS /月のサイクルで起こったことだけでした。未定義の動作のためにコンパイラーが生成したジャンクコードは、「真の」オプションのように見えます。
underscore_d

回答:


146

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

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

C ++ 11ドラフト:

§6.6.3 / 2

関数の最後からフロー[...]すると、値を返す関数で未定義の動作が発生します。

§3.6.1 / 5

制御が ステートメントにmain出会わずに最後に到達した場合return、結果は実行の結果です

return 0;

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


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

-返却式デフォルトで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

このコードは正式には未定義の動作をしており、実際には呼び出し規約アーキテクチャに依存しています。1つの特定のシステムでは、1つの特定のコンパイラーでは、戻り値は、eaxそのシステムのプロセッサーのレジスターに保管されている最後の式評価の結果です。


13
未定義の振る舞いを「許可」と呼ぶことに注意する必要がありますが、それを「禁止」と呼ぶのも間違いです。エラーではなく、診断を必要としないことは、「許可」とまったく同じではありません。少なくとも、あなたの答えは、大丈夫ではなくても大丈夫だと言っているように、少し読みます。
オービットの軽量レース、

4
@Catskul、なぜあなたはその議論を買うのですか?関数の出口点を特定し、それらがすべて値(および宣言された戻り型の値)を返すことを確認することは、簡単ではないにしても、実現可能ではないでしょうか。
BlueBomber 2013年

3
@Catskul、はい、いいえ。静的に型付けされた言語および/またはコンパイルされた言語は、おそらく「法外に高価」と考えるであろう多くのことを行いますが、コンパイル時に一度しか実行しないため、費用はごくわずかです。とは言っても、関数の終了点を特定する必要がある理由はわかりません。関数のASTをトラバースして、returnまたはexit呼び出しを探すだけです。これは線形時間であり、明らかに効率的です。
BlueBomber 2013年

3
@LightnessRacesinOrbit:戻り値のある関数が値ですぐに戻り、常にthrowまたはを介して終了する別の関数を呼び出すことがある場合、戻り値longjmpのないreturn関数の呼び出しの後にコンパイラが到達不能を要求する必要がありますか?それが必要とされない場合はあまり一般的ではなく、そのような場合でもそれを含めるという要件はおそらく面倒ではなかったでしょうが、それを要求しないという決定は合理的です。
スーパーキャット2013

1
@supercat:このような場合でも、超インテリジェントコンパイラは警告やエラーを出しませんが、これも一般的なケースでは本質的に計算不可能であるため、一般的な経験則にとらわれています。ただし、関数の終わりに到達しないことがわかっている場合は、従来の関数処理のセマンティクスから遠く離れています。そうです、これを実行して、安全であることを確認できます。率直に言って、あなたはその時点ではC ++の下の層であり、そのため、とにかくそのすべての保証は無意味です。
オービットのライトネスレース2013年

42

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


2
javacが実際にコードパスをチェックする場合、そのポイントに到達することはできないことがわかりませんか?
Chris Lutz、

3
最初のケースでは、列挙型のすべてのケース(デフォルトのケースまたは切り替え後の復帰が必要)をカバーすることはできません。2番目のケースでは、復帰しないことがわかりませんSystem.exit()
ジョンクーゲルマン

2
javac(それ以外の場合は強力なコンパイラー)がSystem.exit()二度と戻らないことを知るのは簡単です。私はそれを調べ(java.sun.com/j2se/1.4.2/docs/api/java/lang/…)、ドキュメントはそれを「通常は戻らない」とだけ言っています。それはどういう意味だと思いますか
ポール・ビガー

@Paul:それは彼らが編集者を持っていなかったことを意味します。他のすべての言語は、「通常は戻らない」、つまり「通常の復帰メカニズムを使用して復帰しない」と述べています。
Max Lybbert、2009年

1
誰かが列挙型に新しい値を追加するとロジックの正確さが失われるため、少なくとも最初の例に遭遇した場合に警告するコンパイラーを私は間違いなく選択します。大声で不平を言ったり、(おそらくアサーションを使用して)クラッシュしたデフォルトのケースが欲しいです。
インジェクティロ2015

14

つまり、値を返す関数の最後をオフにすること(つまり、明示的になしで終了することreturn)がエラーではないのはなぜですか。

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

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

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


+1ですが、C ++ではreturn末尾のステートメントを省略できませんmain()か?
Chris Lutz、

3
@Chris Lutz:はい、mainその点で特別です。
AnT 2009年

5

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

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

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

C ++ツールチェーンの使用方法を学ぶことは、C ++プログラマーになるための障害ですが、C ++ツールチェーンは通常、エキスパートが作成したものです。


ええ、Makefile私はで実行していますが-Wall -Wpedantic -Werror、これは引数を指定するのを忘れた1回限りのテストスクリプトでした。
モニカの訴訟に資金を提供

2
例として、壊れたGCCブートストラップの-Wduplicated-cond一部を作成し-Wallます。ほとんどのコードで適切であると思われるいくつかの警告は、すべてのコードでは適切ではありません。これがデフォルトで有効になっていない理由です。
ええと、誰かが人形を必要とします2016年

あなたの最初の文は、受け入れられた回答 "Flowing off .... undefined behaviour ..."の引用と矛盾しているようです。または、ubは「合法」と見なされますか?それとも、戻り値の(ない)が実際に使用されない限り、UBではないということですか?私はC ++のケースについて心配しています
idclev 463035818

@ tobi303 int foo() { exit(-1); }int、「intを返すことを要求する」関数からは戻りません。これはC ++では合法です。現在は何も返しません。その関数の終わりに到達することはありません。 実際に最後に到達するのfooは、未定義の動作です。プロセス終了のケースを無視して、int foo() { throw 3.14; }また戻ると主張するintが決して戻らない。
Yakk-Adam Nevraumont 2017年

1
だから私void* foo(void* arg) { pthread_exit(NULL); }は同じ理由で問題ないと思います(その使用が経由の場合のみpthread_create(...,...,foo,...);
idclev 463035818

1

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


1

一部の限られたまれなケースでは、値を返さずに非void関数の終わりをオフにすると便利な場合があります。次のMSVC固有のコードのように:

double pi()
{
    __asm fldpi
}

この関数は、x86アセンブリを使用してpiを返します。GCCでのアセンブリとは異なり、私は使用する方法を知りませんreturn、結果にオーバーヘッドを伴うことなくこれを行う。

私の知る限り、主流のC ++コンパイラーは、明らかに無効なコードに対して少なくとも警告を発する必要があります。の体を作ったらpi()空にすると、GCC / Clangが警告を報告し、MSVCがエラーを報告します。

人々は例外とexitいくつかの回答に言及しました。それらは正当な理由ではありません。例外をスローしたり、を呼び出したりしても、関数の実行フローが終了することexitはありません。そしてコンパイラはそれを知っています:throwステートメントを書くかexit、空の本文を呼び出すと、pi()コンパイラからの警告やエラーが停止します。


MSVCは、voidインラインasmが戻り値レジスタに値を残した後の非関数の終了を明確にサポートします。(st0この場合はx87 、整数の場合はEAX。st0ではなくxmm0でfloat / doubleを返す呼び出し規約では、おそらくxmm0)。この動作の定義はMSVCに固有です。-fasm-blocks同じ構文をサポートするためにclangでさえもこれを安全にしません。__asm {}を
Peter Cordes、

0

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

私が考えることができる1つの例外は、main()関数であり、これはreturnステートメントをまったく必要としません(少なくともC ++では、どちらのC標準も便利ではありません)。リターンがない場合return 0;は、最後のステートメントであるかのように動作します。


1
main()returnC が必要
クリス・ルッツ

@Jefromi:OPは、return <value>;ステートメントのないvoid以外の関数について質問しています
ビル

2
mainは、CおよびC ++では自動的に0を返します。C89は明示的な復帰を必要とします。
ヨハネスシャウブ-litb

1
@Chris:C99ではreturn 0;、末尾にmain()(そしてmain()唯一の)暗黙的要素があります。しかし、return 0;とにかく追加するのは良いスタイルです。
pmg

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
$

5
「on on -Werror」と言っても答えはありません。明らかに、警告とエラーに分類された問題には重大度の違いがあり、gccはこれをそれほど深刻ではないクラスとして扱います。
カスカベル

2
@Jefromi:純粋な言語の観点からは、警告とエラーに違いはありません。コンパイラーは、「診断メッセージ」を発行するためにのみ必要です。コンパイルを停止したり、「エラー」や「警告」を呼び出したりする必要はありません。診断メッセージ(または任意の種類)が発行されたら、決定を行うのは完全にユーザー次第です。
AnT 2009年

1
次に、問題の問題はUBを引き起こします。コンパイラーはUBをキャッチする必要はまったくありません。
AnT 2009年

1
n1256の6.9.1 / 12では、「関数を終了する}に到達し、関数呼び出しの値が呼び出し元によって使用された場合の動作は定義されていません。」
ヨハネスシャウブ-litb

2
@クリス・ルッツ:わかりません。非void関数で明示的な空を使用するreturn;ことは制約違反return <value>;であり、void関数で使用することは制約違反です。しかし、それはトピックではありません。OPは、私が理解しているように、return(関数の最後から制御が流れるのを許可するだけで)なしで非void関数を終了することです。AFAIK、それは制約違反ではありません。標準では、ちょうどそれが常にC ++とCで時々 UBでUBであると言う
ANT

-2

これはc99では制約違反ですが、c89では制約違反ではありません。コントラスト:

c89:

3.6.6.4 returnステートメント

制約

式を含むreturnステートメントは、戻り値の型がである関数には現れませんvoid

c99:

6.8.6.4 returnステートメント

制約

式を含むreturnステートメントは、戻り値の型がである関数には現れませんvoidreturn表現のない文は唯一その戻り値の型である機能に出頭しなければなりませんvoid

--std=c99モードでも、gccは警告のみをスローします(ただし-W、デフォルトまたはc89 / 90で必要な追加のフラグを有効にする必要はありません)。

編集してc89に追加します。「}関数を終了するに到達することはreturn、式なしでステートメントを実行することと同等です」(3.6.6.4)。ただし、c99では動作が定義されていません(6.9.1)。


4
これは明示的なreturnステートメントのみをカバーすることに注意してください。値を返さずに関数の最後から落ちることはカバーしていません。
John Kugelman、

3
C99は「関数を終了する}に到達することは、式なしでreturnステートメントを実行することと同等」であるので、制約違反にならず、診断が不要であることに注意してください。
ヨハネスシャウブ-litb
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.