18

動作しないはずの再帰を示すサンプルプログラムが表示されましたが、動作します。ロジックはかなり明確ですが、再帰された関数呼び出しが返されない場合でも、なぜそれが機能するのですか?return要求されていなくても、コマンドがスタックから抜け出しているようです。これは言語標準ですか、それともgccのものですか?私はそれをWindowsとLinuxでgccでコンパイルされたCとC++で見ました。

#include <iostream>
#include <cstdlib>

using namespace std;

int isprime(int num, int i)
{
   if (i == 1) {
      return 1;
   }
   else {
      if (num % i == 0)
         return 0;
      else
         isprime(num, i-1); // should be returned
   }
}


int main(int argc, char** argv)
{
   int input = atoi(argv[1]);
   cout << input << "\t" << isprime(input, input/2) << "\n";
}
4

4 に答える 4

20

そのようなことは、誤って戻り値が呼び出し元が期待するレジスターにある場合にのみ機能します。これは、コンパイラによって再帰関数として実現されている場合にのみ機能します。技術的には、戻り値を提供しない関数の戻り値を使用することは未定義の動作です。

編集:最新のアーキテクチャでは、可能な値に対する関数の戻り値が特定のハードウェアレジスタに渡されます。関数を再帰的に呼び出すと、すべての場合の下部で、ハードウェアレジスタが期待値に設定されます。再帰からポップアップしたときに、ハードウェアレジスタが変更されない場合は、正しい値になります。

戻り値が(再帰的な)呼び出し元のスタックのある場所に配置される場合、このパターンのすべては機能しません。

いずれにせよ、それらすべては最新のコンパイラーによってキャプチャされ、警告を出す必要があります。そうでない場合は、優れたコンパイラがないか、防御的なコマンドラインオプションを使用しています。

大晦日スペシャル:現実の世界では、このようなコード(を含むreturn)は再帰関数としても実現されません。あまり労力をかけなくても、その関数の反復バリアントを見つけることができます。最大限の最適化を求める場合は、最新の適切なコンパイラーでもそれを見つけることができるはずです。

于 2012-12-31T14:28:47.270 に答える
5

ここでの多くは、「機能する」とはどういう意味ですか?

質問の要点に答えるために、関数の終わりに達すると、returnステートメントが満たされているかどうかに関係なく、関数が戻ります。

いずれにせよ、C ++では、可能なコントロールパスが値を返さない可能性があることを示すコンパイラ警告が表示されると思います。未定義の動作が発生する場合は、次の質問を参照してください: 非void戻り関数から値を返さない

この例は、プライムが見つかり、isPrimeが戻った後、スタックの次の関数も自由に戻ることができるように「機能」すると言えます。isPrimeの戻り値にも依存しないため、プログラムはスタックをバックアップして実行し、何かを出力します。

...ただし、動作は定義されていないため、実際に出力される値はジャンクである可能性があります。入力として素数と一致する0と1が表示されている場合は、すごいです。

これが機能していると思われる場合は、さまざまな値を使用してより広範囲にテストすることを検討します。

また、「デバッグ」設定でビルドしていますか?その場合は、デバッグ設定をオフにしてこれを再試行してください。初期化されていないメモリをクリーンに保つために、余分な作業を行うことがあります。

于 2012-12-31T14:47:31.193 に答える
2

私は何が起こるかを正確に説明することができます:

関数が呼び出され、モジュロ(戻り値0)または再帰の終わり(戻り値1)のいずれかで戻り値に達するまで、関数はそれ自体に再帰的に戻ります。この時点で、関数は呼び出し元であるis_primeに戻ります。ただし、実行する関数にはこれ以上コードがないため、それ以上のアクションなしですぐに戻ります。

ただし、たとえば、printf("Done for %d, %d\n", num, i);is_prime()の呼び出しの後ろに追加することでこれを簡単に破ることができます[ifステートメントにある必要はありません]。または、別の例として、関数の開始/終了時に作成および破棄されるC++オブジェクトを追加します。

あなたはそれが機能することを幸運に思っています。そして、それは非常に壊れやすく、壊れやすいです-別のコンパイラ(または別の最適化設定、またはコンパイラの新しいバージョン、または他の何百万ものもの)でコンパイルすると、壊れることがあります。

于 2012-12-31T15:08:53.337 に答える
-3

returnステートメントを忘れていませんか?通常の再帰では、前にリターンを置く必要がありisprime(num,i-1);ます。

関数は常にintを返す必要があるため、厳密なルールを使用してこれをコンパイルすると、コンパイル警告が表示されるはずですが、現在は返されません(少なくともコンパイラがこれを修正しない場合)。

于 2012-12-31T14:26:52.027 に答える