web-dev-qa-db-ja.com

このC ++スニペットがコンパイルされる理由(非void関数は値を返しません)

今朝、私のライブラリの1つでこれを見つけました。

static tvec4 Min(const tvec4& a, const tvec4& b, tvec4& out)
{
    tvec3::Min(a,b,out);
    out.w = min(a.w,b.w);
}

このメソッドは何も返さず、戻り値の型はvoidではないため、コンパイラエラーが発生するはずです。

頭に浮かぶのは2つだけです

  • このメソッドが呼び出される唯一の場所では、戻り値は使用または保存されません。 (このメソッドはvoid-tvec4戻り値の型はコピーアンドペーストエラーです)

  • デフォルトで構築されたtvec4が作成されていますが、これはC++の他のすべてとは少し違うようです。

これに対処するC++仕様の一部は見つかりませんでした。参考文献(ha)に感謝します。

更新

some状況では、VS2012でエラーが生成されます。詳細を絞り込んでいませんが、それでも面白いです。

129
3Dave

これは 未定義の動作 から C++ 11ドラフト標準 セクション6.6.3returnステートメントパラグラフ2

[...]関数の末尾からのフローは、値なしの戻りと同等です。これにより、値を返す関数で未定義の動作が発生します。 [...]

これは、すべての場合に診断が困難になる可能性があるため、通常はコンパイラがエラーや警告を提供する義務がないことを意味します。これは、セクション1.3.24の標準草案のundefined behaviorの定義からわかります。

[...]許容される未定義の動作は、予測不可能な結果を​​伴う状況を完全に無視することから、環境に特有の文書化された方法での翻訳またはプログラム実行中の動作(診断メッセージの発行の有無にかかわらず)、翻訳の終了、または実行(診断メッセージの発行)。[...]

この場合、gccclangの両方を取得して、-Wallフラグを使用してwanringを生成できますが、次のような警告が表示されます。

警告:コントロールは非void関数の終わりに到達します[-Wreturn-type]

-Werror=return-typeフラグを使用して、この特定の警告をエラーに変えることができます。また、私は個人的なプロジェクトに-Wextra -Wconversion -pedanticを使用したいと思っています。

ComicSansMSがVisual Studioで言及しているように、このコードはデフォルトでエラーである C4716 を生成します。

エラーC4716: 'Min':値を返す必要があります

すべてのコードパスが値を返すわけではない場合、 C4715 を生成します。これは警告です。

154
Shafik Yaghmour

たぶん、質問のwhy部分について詳しく説明します。

結局のところ、C++コンパイラーが関数が戻り値なしで終了するかどうかを判断することは実際には非常に困難です†。明示的なreturnステートメントで終わるコードパスと関数の終わりから落ちるコードパスに加えて、潜在的な例外のスローまたは関数自体のlongjmps、およびそのすべてを考慮する必要があります。呼び出し先。

コンパイラーは、戻り値が欠落しているように見える関数を識別するのは非常に簡単ですが、欠落していることをproveすることはかなり困難ですリターン。この負担のコンパイラベンダーを解除するために、標準では、エラーを生成するためにこれを必要としません。

そのため、コンパイラベンダーは、関数に戻り値がないことを確信している場合は警告を生成でき、ユーザーはコンパイラが実際に間違っていたまれなケースでその警告を自由に無視/マスクできます。

†:一般的な場合、これは 停止問題 と同等であるため、実際にマシンがこれを確実に決定することは不可能です。

58
ComicSansMS

-Wreturn-typeオプションを使用してコードをコンパイルします。

$ g++ -Wreturn-type source.cpp

これにより、警告が得られます。 -Werrorも使用すると、警告をerrorに変更できます。

$ g++ -Wreturn-type -Werror source.cpp

これにより、all警告がエラーに変わることに注意してください。 -Wreturn-typeなどの特定の警告にエラーが必要な場合は、return-type部分なしで-Wと入力するだけです。

$ g++ -Werror=return-type source.cpp

一般に、最も一般的な警告を含む-Wallオプションを常に使用する必要があります。これには、returnステートメントの欠落も含まれます。 -Wallとともに、-Wextraも使用できます。これには、-Wallに含まれない他の警告も含まれます。

22
Nawaz

たぶん、質問のwhy部分に関する追加の詳細な説明。

C++は、既存のCコードの非常に大きなボディが最小限の変更でコンパイルされるように設計されました。残念ながら、C自体はvoidキーワードさえも持たず、代わりにintのデフォルトの戻り値型に依存していた最も古い先行標準Cと同様の義務を負っていました。通常、C関数は値を返し、ALGOL/Pascal/Basicプロシージャに表面的に類似したコードがreturnステートメントなしで記述された場合、関数は内部でスタックに残っているゴミを返します。呼び出し側も呼び出し先も、信頼できる方法でガベージの値を割り当てません。ガベージがすべての呼び出し元によって無視される場合、すべてが正常であり、C++はそのようなコードをコンパイルするという道徳的義務を継承します。

(返された値が呼び出し元によって使用される場合、コードは初期化されていない変数の処理と同様に非決定的に動作する可能性があります。違いは、Cの仮定の後継言語でコンパイラによって確実に識別できますか?これはほとんど不可能です。呼び出し元と呼び出し先は異なるコンパイル単位にいる場合があります。)

暗黙のintは、ここに含まれるCのレガシーの一部にすぎません。 「ディスパッチャ」関数は、パラメータに応じて、一部のコードブランチからさまざまな型を返し、他のコードブランチからは有用な値を返さない場合があります。通常、このような関数は、可能な型のいずれかを保持するのに十分な長さの型を返すように宣言され、呼び出し元はunionからキャストまたは抽出する必要がある場合があります。

そのため、おそらく最も深い原因は、値を返さないプロシージャは、そうする関数の重要ではない特別なケースであるというC言語の作成者の信念です。この問題は、最も古いC方言の 関数呼び出しの型安全性に焦点が当てられていない によって悪化しました。

C++はCのいくつかの最悪の側面との互換性を壊しましたが( )、値なしのreturnステートメント(または関数の最後の暗黙的な値なしのreturn)をコンパイルする意思はそれらの1つではありません。

21
Jirka Hanika

既に述べたように、これは未定義の動作であり、コンパイラの警告が表示されます。私が働いたほとんどの場所では、コンパイラー設定を有効にして、警告をエラーとして扱う必要があります。これにより、すべてのコードをエラー0および警告0でコンパイルする必要があります。これは、なぜそれが良いアイデアであるかの良い例です。

12
Zac Howland

これは、物事に柔軟に対応する傾向があり、Cにより近い傾向がある標準C++ルール/機能です。

しかし、コンパイラ、GCCまたはVSについて話すとき、それらはより専門的な使用とさまざまな開発目的のためのものであるため、ニーズに応じてより厳格な開発ルールを適用します。

私の個人的な意見では、言語はすべて機能とその使用法に関するものであるのに対し、コンパイラはニーズに応じて最適かつ最適な使用方法のルールを定義しているため、これは理にかなっています。

上記の投稿で述べたように、コンパイラは時々エラーを出し、時には警告を出し、これらの警告をスキップするなどのオプションもあり、言語とその機能を私たちに最適な方法で使用する自由を示します。

2
Bhupesh Pant

これに加えて、returnステートメントを使用せずに結果を返すこの動作に言及する他のいくつかの質問があります。 1つの簡単な例は次のとおりです。

int foo(int a, int b){ int c = a+b;}

int main(){
   int c = 5;
   int d = 5;

   printf("f(%d,%d) is %d\n", c, d, foo(c,d)); 

   return 0;
}

この異常は、スタックのプロパティによるものである可能性がありますか?

ゼロアドレスマシン

アドレスがゼロのマシンでは、両方のオペランドがデフォルトの場所にあると想定されますの場所。これらのマシンはスタックを入力オペランドのソースとして使用し、結果はスタックに戻ります。スタックはLIFO(後入れ先出し)データ構造であり、すべてのプロセッサがゼロアドレスマシンであるかどうかに関係なく。名前が示すように、最後のアイテムはstackは、スタックから最初に取り出される項目です。このタイプのマシンでのすべての操作は、必要な入力オペランドがスタックの上位2つの値であると想定しています。操作の結果は、スタック

さらに、メモリにアクセスしてデータを読み書きするために、同じレジスタがデータのソースおよびデスティネーション(DS(データセグメント)レジスタ)として使用され、最初に計算に必要な変数を格納し、次に返された結果を格納します。

注意:

この回答では、すでにコンテキストがあり、十分に広い範囲でカバーされているため、マシン(命令)レベルでの奇妙な動作の1つの可能な説明を議論したいと思います。

0
Ziezi