2012-12-31 13 views
18

私は再帰を実証するためのサンプルプログラムを見せましたが、それはうまくいかないようですが、そうではありません。ロジックははっきりしていますが、再帰関数呼び出しが返されない場合でもなぜ機能しますか? returnコマンドが要求されていなくてもスタックからブレークアウトするようです。これは言語標準ですか、gccのものですか?私はWindowsとLinux上でgccでコンパイルされたCとC++でそれを見た。なぜ再帰呼び出しが明示的なreturn文なしでスタックからブレークするのですか?

#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"; 
} 
+12

未定義の動作には、偶発的に動作するコードが含まれています。関数呼び出しは、正しいCPUレジスタに戻り値を残します。 –

+2

私は質問の投票の理由を見ません。多くの初心者が持つことができるのはかなり良い疑いです。 – varevarao

+3

@varevarao:初心者の「疑問」は研究努力によって満たされます。それは、私はupvotedと述べた。 –

答えて

20

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

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

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

いずれにしても、それらのすべては現代のコンパイラによってキャプチャされ、警告が表示されます。それが良いコンパイラを持っていないか、あまりにも守備のコマンドラインオプションを使用している場合。

大晦日の特別:現実の世界ではは、(returnで)このようなコードでも再帰関数として実現されることはありません。それほど多くの努力を払わなければ、その関数の反復的な変形を見つけることができます。そして、近代的なまともなコンパイラは最大の最適化を求めるならばそれを見つけることができます。

+1

_戻り値は、呼び出し元が期待するレジスタに存在します。これは少し説明できますか? – varevarao

-3

return文を忘れていませんか?通常の再帰の場合は、isprime(num,i-1);の前に戻り値を設定する必要があります。

これは厳密な規則を使ってコンパイルすると、関数が常にintを返さなければならないので、コンパイル警告が出るはずです。少なくともコンパイラーがこれを修正していない場合はそうではありません。

+3

これはまさに質問です。私はそれがリターンステートメントを必要とすると思うだろうが、それはしません。 – mmdanziger

+0

@ mmdanzigerええ、ただそれを実現しました。私はこれが動作すれば、Jensが説明したように未定義の動作だと思います。いくつかの厳しいオプションを使用してコンパイルすると、おそらく警告が表示されます。 – Aloys

+1

g ++でコンパイルする-Wallは "制御が非空白関数の終わりに達する"と警告します –

5

ここではたくさんのことが、「それは機能する」という意味に依存しますか?

あなたの質問の要点に答えようとすると、関数の終わりに達すると、return文が満たされているかどうかに関わらず関数が返されます。

可能なコントロールパスがC++で値を返さないかもしれないことをコンパイラの警告がどのような速度で伝えてくるのを期待しています。 not returning a value from a non-void returning function

この例は、素数が見つかった後にisPrimeが返された後、次の関数が返されても自由であると言います。 isPrimeの戻り値のどちらにも依存しないので、プログラムはスタックを実行して何かを出力します。

...動作が定義されていないため、実際に出力される値は迷惑になる可能性があります。 0 & 1が入力としての素数と一貫しているのを見ているなら、うわー。

これが機能していると思われる場合は、より幅広くさまざまな値でテストします。

また、「デバッグ」設定でビルドしていますか?デバッグ設定をオフにして再度試してみると、時には初期化されていないメモリをきれいに保つために特別な作業が必要になることがあります。

2

私が起こるまさに説明することができます:

関数が呼び出され

、そしてそれはモジュロ(リターン0)または再帰の終了(リターン1)のいずれかのリターンに到達するまで、それは自分自身に戻って再帰。この時点で、関数は呼び出し元に戻ります。呼び出し元はis_primeです。しかし、実行する関数にはコードがなくなるので、それ以上のアクションは起こりません。

しかし、is_prime()の呼び出しの後ろに例えばprintf("Done for %d, %d\n", num, i);を追加することで、これを簡単に破ることができます[if文に含める必要はありません]。別の例として、関数の入口/出口で作成および破棄されるC++オブジェクトを追加することもできます。

あなたは運が良かっただけです。そして、それは非常に壊れやすいし、別のコンパイラ(または異なる最適化設定、または新しいバージョンのコンパイラ、または他の何百万もの)でコンパイルするのは簡単です。