6

次のような場合、 IL は常にメソッドのcallvirt命令を使用するとは限りません。virtual

class MakeMeASandwich{
  public override string ToString(){
    return base.ToString();
  }
}

この場合、 is かどうかを確認するために where が生成され、それ以外の場合はスローされる代わりに、IL が生成されると言われていcallます。callvirtcallvirtvariablenullNullReferenceException

  1. callvirtの代わりに を使用すると、スタック オーバーフローまで再帰呼び出しが発生するのはなぜcallですか?
  2. が使用されている場合call、メソッドの呼び出しに使用するインスタンス変数が null かどうかをいつチェックしますか?
4

5 に答える 5

10

call の代わりに callvirt を使用すると、スタック オーバーフローまで再帰呼び出しが発生するのはなぜですか?

そのため、コードは次とまったく同じです。

override string ToString()
{
    return this.ToString();
}

指定されたメソッドが ToString の最もオーバーライドされたバージョンである場合、これは明らかに無限再帰です。

call が使用されている場合、メソッドを呼び出すために使用するインスタンス変数が null かどうかを確認するのはなぜですか?

質問は虚偽を前提としているため、回答できません。call 命令は、レシーバーへの参照が null かどうかをチェックしないため、call 命令が null をチェックする理由を尋ね意味がありません。

それをいくつかのより良い質問に言い換えてみましょう:

C# コンパイラはどのような状況で call と callvirt を生成しますか?

C# コードが仮想メソッドで非仮想呼び出しを実行している場合、コンパイラは callvirt ではなく呼び出しを生成する必要があります。これが実際に発生するのは、使用して仮想メソッドを呼び出すときだけです。base

C# コードが仮想呼び出しを実行している場合、コンパイラはcallvirt を生成する必要があります。

C# コードが非仮想メソッドで非仮想呼び出しを実行している場合、コンパイラは call または callvirt のいずれかを生成することを選択できます。どちらでも動作します。C# コンパイラは通常、callvirt の生成を選択します。

call 命令は自動的に null チェックを行いませんが、callvirt は行います。C# コンパイラが callvirt の代わりに呼び出しを生成することを選択した場合、null チェックも生成する義務がありますか?

いいえ。レシーバーが null ではないことが既にわかっている場合、C# コンパイラは null チェックをスキップできます。たとえば(new C()).M()、非仮想メソッド M について述べた場合、コンパイラがcallnull チェックなしで命令を生成することは合法です。(1) メソッドは仮想ではないため、callvirt;である必要はありません。使用するかどうかを選択できますcallvirt。また、(2)new C()は決して null ではないことがわかっているため、null チェックを生成する必要はありません。

C# コンパイラは、レシーバーが null でないことを認識していない場合、callvirt を生成するか、null チェックとそれに続く呼び出しを生成します。

于 2012-04-18T16:18:04.477 に答える
3
  1. callどのメソッドを呼び出すかがすぐにわかっているため、実行時に参照する必要がないため使用されます(callvirtコードが最も具体的なクラスで定義されたメソッドを呼び出し、スタックオーバーフローを引き起こします)。

  2. callvirtnull チェックを意味しますが、そうではありcallません。

于 2012-04-18T16:12:26.037 に答える
3
  1. callvirt は、オブジェクトの実装ではなく、MakeMeASandwich の実装を呼び出します。これがスタックオーバーフローを取得する方法です。

  2. 最初の呼び出しは、参照が null でないことを確立する callvirt を使用して行われました。コントロールがこの ToString 実装内にある場合、オブジェクトがあることが既にわかっています。

于 2012-04-18T16:12:12.640 に答える
2

call の代わりに callvirt を使用すると、スタック オーバーフローまで再帰呼び出しが発生するのはなぜですか?

他の人が答えたようにcallvirt、メソッドを仮想的に呼び出します。まるであなたが書いたかのように

public override string ToString() {
    return ToString();
}

call が使用されている場合、メソッドを呼び出すために使用するインスタンス変数が null かどうかを確認するのはなぜですか?

this他の人は、 null ではないことを既に知っていると述べています。それは正しくありません。を呼び出すために を使用する場合 、はい、null にすることはできません。ただし、関数を呼び出すために使用される要件はありません。他の言語では、オペコードが生成されるような方法で呼び出しを記述できます。その場合、null チェックは実行されません。C++/CLI では で許可されていると思いますが、完全にはわかりません。確認しない限り、それが nullではないことを確認することはできません。callvirtMakeMeASandwich.ToStringthiscallvirtcallmakeMeASandwich->MakeMeASandwich::ToString()this

于 2012-04-18T16:18:26.890 に答える
2

Callvirt は、利用可能な最も派生したメソッドを呼び出します。この場合、これは MakeMeASandwich.ToString() です。

callvirt の目的は、null をチェックするだけでなく、仮想メソッド呼び出しを実行することでもあります。

于 2012-04-18T16:12:19.373 に答える