6

私はPerlプロジェクトを持っていますが、循環パッケージ呼び出しを行うことで問題が発生しました。以下のコードは問題を示しています。

これが実行されると、各パッケージは、コンピュータのすべてのメモリが消費されてロックされるまで、他のパッケージを呼び出します。これは悪い設計であり、このような循環呼び出しを設計で行うべきではないことに同意しますが、私のプロジェクトは十分に大きいため、実行時にこれを検出したいと思います。

弱体化関数とData::Structure :: Utilについて読みましたが、循環パッケージロードがあるかどうかを検出する方法がわかりません(反復ごとに新しいコピーが作成されて保存されるため、想定しています) $ thisハッシュの各コピーで)。何か案は?

use system::one;

my $one = new system::one(); 

package system::one;

use strict;

use system::two;

sub new {
  my ($class) = @_; 
  my $this = {};  
  bless($this,$class); 
  # attributes
  $this->{two} = new system::two();
  return $this; 
} 

package system::two;

use strict;

use system::one;

sub new {
  my ($class) = @_; 
  my $this = {};  
  bless($this,$class); 
  # attributes
  $this->{one} = new system::one();
  return $this; 
} 
4

5 に答える 5

4

これらが別々のパッケージにあるという事実は、これが無限に実行され、利用可能なすべてのリソースを消費するという事実とはまったく関係ありません. 2 つのメソッドを相互に呼び出しています。これは循環参照ではなく、再帰であり、同じものではありません。特に、weakenまったく役に立ちません。以下からまったく同じ効果が得られます。

sub a {
    b();
}

sub b {
    a();
}

a();

これを回避する最善の方法は、そうしないことです。さらに便利なことに、再帰関数を作成する必要がある場合は、再帰チェーンで複数の関数を使用するのではなく、単に 1 つの関数を使用するようにしてください。そうすれば、呼び出しが終了する場所を頭の中で追跡しやすくなります。

このようなことが起こっているかどうかを検出する方法については、再帰の深さで変数をインクリメントし、深さが特定の値を超えた場合に終了 (または戻る) などの簡単なことを行う必要があります。しかし、実際にはそれに頼る必要はありません。これは、whileループを記述し、そこでインクリメントを使用して、関数が制御不能にならないようにするのと似ています。セットがいつどのように終了するかがわからない限り、セットを再帰しないでください。

もう 1 つの関連する質問は、そもそも何を達成しようとしているのかということです。

于 2009-04-10T19:46:43.790 に答える
4

ここにもいくつかのコードがあります。:)

sub break_recursion(;$) {
    my $allowed = @_ ? shift : 1;
    my @caller = caller(1);
    my $call = $caller[3];
    my $count = 1;
    for(my $ix = 2; @caller = caller($ix); $ix++) {
        croak "found $count levels of recursion into $call"
            if $caller[3] eq $call && ++$count > $allowed;
    }
}

sub check_recursion(;$) {
    my $allowed = @_ ? shift : 1;
    my @caller = caller(1);
    my $call = $caller[3];
    my $count = 1;
    for(my $ix = 2; @caller = caller($ix); $ix++) {
        return 1
            if $caller[3] eq $call && ++$count > $allowed;
    }
    return 0;
}

これらは次のように呼ばれます。

break_recursion(); # to die on any recursion
break_recursion(5); # to allow up to 5 levels of recursion
my $recursing = check_recursion(); # to check for any recursion
my $recursing = check_recursion(10); # to check to see if we have more than 10 levels of recursion.

これらをCPANするかもしれないと思います。誰かがそれについて考えている場合は、共有してください。

于 2009-04-10T20:26:32.010 に答える
3

次のように caller() を使用してコール スタックを調べる、break_constructor_recursion() のようなルーチンを作成することをお勧めします。

今私を呼んだパッケージのどのメソッドを調べてください。

残りのコール スタックを調べて、同じパッケージ内の同じメソッドがさらに上にあるかどうかを確認します。

もしそうなら、適切なもので die() してください。

次に、コンストラクターに break_constructor_recursion() への呼び出しを追加します。コンストラクターが内部から呼び出されている場合は、爆発します。

現在、これは誤検知をスローする可能性があります。コンストラクターを内部で正当に呼び出すことは不可能ではありません。それに問題がある場合は、エラーを特定する前に、コンストラクターの N 個の追加の出現を探すだけだと思います。スタックに system::two::new() への呼び出しが 20 回ある場合、再帰していない可能性はかなり低くなります。

于 2009-04-10T19:48:35.750 に答える
2

二重再帰の古典的なブレークは、状態変数を使用して、既に関数内にいるかどうかを判断することです。

{
    my $in_a;
    sub a {
        return if $in_a; #do nothing if b(), or someone b() calls, calls a()
        $in_a = 1;
        b();
        $in_a = 0;
    }
}

$in_aが true の場合は何でもできますが、 dieing または return が一般的です。Perl 5.10 以降を使用している場合はstate、関数を独自のスコープにネストする代わりに関数を使用できます。

sub a {
    state $in_a;
    return if $in_a; #do nothing if b(), or someone b() calls, calls a()
    $in_a = 1;
    b();
    $in_a = 0;
}
于 2009-04-10T20:14:53.410 に答える
1

use warnings;

警告なし:

#!/usr/bin/perl 

use strict;

sub foo {
    foo(); 
}

foo();

-

$ perl script.pl
^C # 死後

警告あり:

#!/usr/bin/perl 

use strict;
use warnings;

sub foo {
    foo(); 
}

foo();

-

$ perl script.pl
script.pl 7 行目のサブルーチン「main::foo」での深い再帰。
^C # 死後

常に警告を常に使用してください。

use warnings FATAL => qw( recursion );

#!/usr/bin/perl 

use strict;
use warnings FATAL => qw( recursion );

sub foo {
    foo(); 
}

foo();

-

$ perl script.pl
script.pl 7 行目のサブルーチン「main::foo」での深い再帰。
$
于 2009-04-12T19:13:25.507 に答える