문제

CIL 지침 "Call"과 "Callvirt"의 차이점은 무엇입니까?

도움이 되었습니까?

해결책

call 비 공인, 정적 또는 슈퍼 클래스 방법을 호출하는 것입니다. 즉, 통화 대상은 재정의 대상이 아닙니다. callvirt 가상 메소드를 호출하는 것입니다 this 메소드를 무시하는 서브 클래스이며 서브 클래스 버전은 대신 호출됩니다).

다른 팁

런타임이 실행될 때 call 지침 정확한 코드 (메소드)를 호출하고 있습니다. 그것이 어디에 있는지에 대한 의문의 여지가 없습니다. IL이 Jitted되면 전화 사이트의 결과 기계 코드는 무조건적입니다. jmp 지침.

대조적으로 callvirt 명령어는 다형성 방식으로 가상 방법을 호출하는 데 사용됩니다. 메소드 코드의 정확한 위치는 각 호출마다 런타임에 결정되어야합니다. 결과적인 코드는 vtable 구조를 통한 약간의 간접성이 포함됩니다. 따라서 통화는 실행이 느리지 만 다형성 호출을 허용한다는 점에서 더 유연합니다.

컴파일러가 방출 될 수 있습니다 call 가상 방법에 대한 지침. 예를 들어:

sealed class SealedObject : object
{
   public override bool Equals(object o)
   {
      // ...
   }
}

통화 코드 고려 :

SealedObject a = // ...
object b = // ...

bool equal = a.Equals(b);

하는 동안 System.Object.Equals(object) 가상 방법이며,이 사용에서는 과부하를위한 방법이 없습니다. Equals 존재하는 방법. SealedObject 봉인 된 클래스이며 서브 클래스를 가질 수 없습니다.

이런 이유로 .net 's sealed 클래스는 비밀화되지 않은 상대보다 더 나은 방법 파견 성능을 가질 수 있습니다.

편집하다: 내가 틀린 것으로 밝혀졌다. C# 컴파일러는 객체의 기준 ( this 메소드 내에서)는 null 일 수 있습니다. 대신 방출됩니다 callvirt 필요한 경우 무효 점검을하고 던진다.

이것은 실제로 반사기를 사용하여 .NET 프레임 워크에서 찾은 기괴한 코드를 실제로 설명합니다.

if (this==null) // ...

컴파일러가 this 포인터 (local0), CSC만이이를 수행하지 않습니다.

그래서 나는 추측한다 call 클래스 정적 메소드 및 스트러크에만 사용됩니다.

이 정보가 주어지면 지금은 나에게 보입니다 sealed API 보안에만 유용합니다. 나는 찾았다 다른 질문 그것은 수업을 봉인 할 수있는 성과 이점이 없다는 것을 암시하는 것 같습니다.

편집 2 : 이것에 더 많은 것이 보입니다. 예를 들어 다음 코드는 a call 지침:

new SealedObject().Equals("Rubber ducky");

분명히 그러한 경우에는 객체 인스턴스가 널가 될 가능성이 없습니다.

흥미롭게도 디버그 빌드에서 다음 코드가 방출됩니다. callvirt:

var o = new SealedObject();
o.Equals("Rubber ducky");

두 번째 줄에 중단 점을 설정하고 값을 수정할 수 있기 때문입니다. o. 릴리스 빌드에서 나는 전화가 call 보다는 callvirt.

불행히도 내 PC는 현재 실시되지 않지만 다시 실험하면 다시 실험하겠습니다.

이러한 이유로 .NET의 봉인 된 클래스는 밀봉되지 않은 상대보다 더 나은 방법 파견 성능을 가질 수 있습니다.

불행히도 이것은 사실이 아닙니다. Callvirt는 유용하게 만드는 다른 일을합니다. 객체에 호출 된 메소드가있는 경우 CallVirt는 객체가 존재하는지 확인하고 NullReferenceException을 던지지 않으면됩니다. 객체 참조가 없어도 전화는 단순히 메모리 위치로 이동하여 해당 위치에서 바이트를 실행하려고 시도합니다.

이것이 의미하는 바는 CallVirt가 클래스에 C# 컴파일러 (VB에 대해서는 확실하지 않음)에 의해 항상 사용되며, Call은 항상 스트러크에 사용됩니다 (절대 널 또는 서브 클래스가 없기 때문에).

편집하다 Drew Noakes에 대한 응답으로 : 예, 컴파일러가 모든 수업에 대한 전화를 걸도록 할 수 있지만 다음과 같은 매우 구체적인 경우에만 다음과 같습니다.

public class SampleClass
{
    public override bool Equals(object obj)
    {
        if (obj.ToString().Equals("Rubber Ducky", StringComparison.InvariantCultureIgnoreCase))
            return true;

        return base.Equals(obj);
    }

    public void SomeOtherMethod()
    {
    }

    static void Main(string[] args)
    {
        // This will emit a callvirt to System.Object.Equals
        bool test1 = new SampleClass().Equals("Rubber Ducky");

        // This will emit a call to SampleClass.SomeOtherMethod
        new SampleClass().SomeOtherMethod();

        // This will emit a callvirt to System.Object.Equals
        SampleClass temp = new SampleClass();
        bool test2 = temp.Equals("Rubber Ducky");

        // This will emit a callvirt to SampleClass.SomeOtherMethod
        temp.SomeOtherMethod();
    }
}

노트 이것이 작동하기 위해 수업을 봉인 할 필요는 없습니다.

따라서이 모든 것이 사실이라면 컴파일러가 전화를 방출하는 것처럼 보입니다.

  • 메소드 호출은 객체 생성 직후입니다
  • 이 방법은 기본 클래스에서 구현되지 않습니다

MSDN에 따르면 :

부르다:

통화 명령어는 명령어와 함께 전달 된 메소드 디스크립터로 표시된 메소드를 호출합니다. 메소드 디스크립터는 호출 방법을 나타내는 메타 데이터 토큰입니다 ... 메타 데이터 토큰은 통화가 정적 메소드, 인스턴스 방법, 가상 메소드 또는 전역 기능인지 여부를 결정하기에 충분한 정보를 전달합니다. 이 모든 경우에 대상 주소는 메소드 설명자로부터 전적으로 결정됩니다. (대상 주소는 CallVirt 앞에 푸시 된 인스턴스 참조의 런타임 유형에 따라 다가오는 가상 메소드를 호출하기위한 CallVirt 명령과 대조하십시오).

callvirt:

CallVirt 명령은 객체에서 늦은 메소드를 호출합니다. 그건, 이 방법은 메소드 포인터에 표시되는 컴파일 타임 클래스 대신 OBJ의 런타임 유형에 따라 선택됩니다.. CallVirt를 사용하여 가상 및 인스턴스 메소드를 모두 호출 할 수 있습니다.

따라서 기본적으로 다른 경로가 객체의 인스턴스 메소드를 호출하여 재정의 여부를 호출합니다.

전화 : 변수 -> 변수 객체 -> 메소드를 입력하십시오

CallVirt : 변수 -> 객체 인스턴스 -> 사물 객체 -> 메소드를 입력하십시오

이전 답변에 추가 할 가치가있는 한 가지는 "IL Call"이 실제로 실행되는 방식에 대한 얼굴이 하나만 있고 "IL Callvirt"가 어떻게 실행되는지에 대한 두 얼굴이있는 것 같습니다.

이 샘플 설정을 수행하십시오.

    public class Test {
        public int Val;
        public Test(int val)
            { Val = val; }
        public string FInst () // note: this==null throws before this point
            { return this == null ? "NO VALUE" : "ACTUAL VALUE " + Val; }
        public virtual string FVirt ()
            { return "ALWAYS AN ACTUAL VALUE " + Val; }
    }
    public static class TestExt {
        public static string FExt (this Test pObj) // note: pObj==null passes
            { return pObj == null ? "NO VALUE" : "VALUE " + pObj.Val; }
    }

먼저, finst () 및 fext ()의 실체는 100% 동일하고 Opcode-to-Opcode입니다 (하나는 "인스턴스"로 선언되고 다른 "static"을 제외하고는 finst ()가 호출됩니다. "CallVirt"및 "Call"이 포함 된 Fext ().

둘째, Finst () 및 fvirt ()는 "CallVirt"로 호출됩니다. 하나는 가상이지만 다른 하나는 그렇지 않지만 실제로 실행할 "동일한 CallVirt"는 아닙니다.

다음은 Jitting 이후에 발생하는 일입니다.

    pObj.FExt(); // IL:call
    mov         rcx, <pObj>
    call        (direct-ptr-to) <TestExt.FExt>

    pObj.FInst(); // IL:callvirt[instance]
    mov         rax, <pObj>
    cmp         byte ptr [rax],0
    mov         rcx, <pObj>
    call        (direct-ptr-to) <Test.FInst>

    pObj.FVirt(); // IL:callvirt[virtual]
    mov         rax, <pObj>
    mov         rax, qword ptr [rax]  
    mov         rax, qword ptr [rax + NNN]  
    mov         rcx, <pObj>
    call        qword ptr [rax + MMM]  

"Call"과 "CallVirt [instance]의 유일한 차이점은 "CallVirt [instance]는 인스턴스 함수의 직접 포인터를 호출하기 전에 *POBJ의 One Byte에 의도적으로 액세스하려고한다는 것입니다 (예외를 던질 수 있습니다. "). 바로 거기에 그리고 ").

따라서, 당신이 "확인 부분"을 작성 해야하는 횟수에 짜증이 나면

var d = GetDForABC (a, b, c);
var e = d != null ? d.GetE() : ClassD.SOME_DEFAULT_E;

"if (this == null) retud some_default_e;" classd.gete () 자체로 내려 가면 ( "il callvirt [instance]Semantics는이 작업을 수행하는 것을 금지하지만) .gete ()를 확장 함수로 이동하면 .gete ()로 자유롭게 밀 수 있습니다. ( "IL Call"시맨틱이 허용하지만, 아아, 개인 회원 등에 대한 접근성을 잃는 등).

즉, "CallVirt [instance]의 실행은 "CallVirt [Virtual]보다 "Call"과 더 공통적으로 제공됩니다. 후자는 기능의 주소를 찾기 위해 트리플 간접을 실행해야 할 수 있기 때문입니다. (TypEdef베이스에 대한 간접, 그런 다음 Base-VTAB-또는 SOME-INTERFACE로, 실제 슬롯으로)

이것이 도움이되기를 바랍니다, 보리스

위의 답변에 추가하면 CallVirt IL 명령이 모든 인스턴스 방법에 대해 생성되고 정적 메소드에 대해 Call IL 명령이 생성 될 수 있도록 변경 사항이 오래되었다.

참조 :

pluralsight 코스 "C# 언어 내부 - Bart de Smet의 Part 1 (비디오 - 간단히 말해서 CLR IL의 통화 지침 및 통화 스택)

그리고 또한https://blogs.msdn.microsoft.com/ericgu/2008/07/02/why-does-c-always-us-callvirt/

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top