2014-12-21 61 views
4

我正在學習C#IL簡單的例子,無法理解一些東西。我有很簡單的程序:C#IL - 調用構造函數

void Main() 
{ 
    C c = new C(1); 
} 
class C 
{ 
    public C(){} 
    public C(int i){} 
} 

有CIL:

IL_0001: ldc.i4.1  
IL_0002: newobj  UserQuery+C..ctor 
IL_0007: stloc.0  // c 

C..ctor: 
IL_0000: ldarg.0  
IL_0001: call  System.Object..ctor 
IL_0006: nop   
IL_0007: nop   
IL_0008: nop   
IL_0009: ret   

C..ctor: 
IL_0000: ldarg.0  
IL_0001: call  System.Object..ctor 
IL_0006: nop   
IL_0007: nop   
IL_0008: nop   
IL_0009: ret 

我不明白,虛擬機將如何區分哪一個構造函數應該是調用。有兩個相同的標籤,唯一的區別似乎是在main中推入參數。調用構造函數時有更深層的東西嗎?也許編譯器提供了一些元數據來區分應該調用哪一個?

因此,讓我們假設這樣的:

void Main() 
{ 
    C c = new C(1); 
} 
class C 
{ 
    public C(){} 
    public C(int i){ i += 1;} 
} 

IL_0001: ldc.i4.1  
IL_0002: newobj  UserQuery+C..ctor 
IL_0007: stloc.0  // c 

C..ctor: 
IL_0000: ldarg.0  
IL_0001: call  System.Object..ctor 
IL_0006: nop   
IL_0007: nop   
IL_0008: nop   
IL_0009: ret   

C..ctor: 
IL_0000: ldarg.0  
IL_0001: call  System.Object..ctor 
IL_0006: nop   
IL_0007: nop   
IL_0008: ldarg.1  
IL_0009: ldc.i4.1  
IL_000A: add   
IL_000B: starg.s  01 
IL_000D: nop   
IL_000E: ret 

現在,如何區分調用哪一個,在標籤的水平,我們無法分辨它。

+0

這是關於對構造函數或構造函數體的調用嗎? - 你顯示兩個構造函數體是平等的,但你只顯示對構造函數之一的調用。 – Theraot

+0

@Theraot - 我想知道VM如何知道這個子程序調用哪一個,特別是代碼中沒有提供什麼讓我們區分。它應該是第一個C..ctor還是第二個? – Puchacz

+5

您需要使用更好的反彙編程序。 Ildasm.exe非常清楚使用哪個構造函數。請記住,'ldarg.0'操作碼是用於隱藏* this *參數,而不是構造函數參數。 –

回答

4

我就做這個實驗...

我用下面的代碼:

class Program 
{ 
    static void Main() 
    { 
     CallConstructorA(); 
     CallConstructorB(); 
    } 

    static void CallConstructorA() 
    { 
     GC.KeepAlive(new C()); 
    } 

    static void CallConstructorB() 
    { 
     GC.KeepAlive(new C(1)); 
    } 
} 

class C 
{ 
    public C() { } 
    public C(int i) 
    { 
     GC.KeepAlive(i); 
    } 
} 

以下是MSIL與Telerik的JustDecompile得到了該類計劃:

.class private auto ansi beforefieldinit Test.Program 
    extends [mscorlib]System.Object 
{ 
    .method public hidebysig specialname rtspecialname instance void .ctor() cil managed 
    { 
     IL_0000: ldarg.0 
     IL_0001: call instance void [mscorlib]System.Object::.ctor() 
     IL_0006: ret 
    } 

    .method private hidebysig static void CallConstructorA() cil managed 
    { 
     IL_0000: nop 
     IL_0001: newobj instance void Test.C::.ctor() 
     IL_0006: call void [mscorlib]System.GC::KeepAlive(object) 
     IL_000b: nop 
     IL_000c: ret 
    } 

    .method private hidebysig static void CallConstructorB() cil managed 
    { 
     IL_0000: nop 
     IL_0001: ldc.i4.1 
     IL_0002: newobj instance void Test.C::.ctor(int32) 
     IL_0007: call void [mscorlib]System.GC::KeepAlive(object) 
     IL_000c: nop 
     IL_000d: ret 
    } 

    .method private hidebysig static void Main() cil managed 
    { 
     .entrypoint 
     IL_0000: nop 
     IL_0001: call void Test.Program::CallConstructorA() 
     IL_0006: nop 
     IL_0007: call void Test.Program::CallConstructorB() 
     IL_000c: nop 
     IL_000d: ret 
    } 
} 

所以你可以看到電話是不同的...

第一個說:

 IL_0001: newobj instance void Test.C::.ctor() 

第二個說:

 IL_0002: newobj instance void Test.C::.ctor(int32) 

所以,我gessing這是你的反編譯器是不顯示的中間代碼的所有細節。事實上,我在LINQPad上嘗試過類似的代碼,並且這兩個調用看起來都很相似。


有關如何在二進制文件中完成註釋的細節......老實說,我不知道。

+0

我使用LINQPad 4進行反彙編。認爲它會顯示所有細節...... – Puchacz

+1

「關於如何在二進制文件中完成註釋的細節......老實說我不知道​​。」您可以隨時閱讀[規範](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf),具體爲§III.4.21newobj,§II.22.25MethodRef和§ II.23.2 Blob和簽名。 – svick

1

我遠在IL

專家最簡單的方法找出如何,編譯你的例子沒有通過1給構造

這條線就會消失:IL_0001: ldc.i4.1這意味着,我認爲,它不會將參數傳遞給構造函數。

通過傳遞9而不是1做一遍,這個IL_0001: ldc.i4.1IL_0001: ldc.i4.s 9

2

更換使用您所提供的完全相同的代碼..我看到下面的IL,這清楚地表明構造函數的重載被稱爲 -

.method private hidebysig static void Main(string[] args) cil managed 
{ 
    .entrypoint 
    // Code size  9 (0x9) 
    .maxstack 1 
    .locals init ([0] class ConsoleTest.C c) 
    IL_0000: nop 
    IL_0001: ldc.i4.1 
    IL_0002: newobj  instance void ConsoleTest.C::.ctor(int32) 
    IL_0007: stloc.0 
    IL_0008: ret 
} // end of method Program::Main 

注意instance void ConsoleTest.C::.ctor(int32)

而關於IL,有疑問時,可以而且應該經常看的文檔。 由於您可以在運行時發送IL,因此大多數情況下,Emit API的文檔都會爲您提供足夠的信息。例如,在NewObj情況下,明確規定,構造ConstructorInfo需要NewObj操作碼

「彙編格式newobj ctor

「下面的Emit方法重載可以使用newobj操作碼:ILGenerator.Emit(OpCode, ConstructorInfo)

+0

謝謝,我用於反彙編的程序並沒有顯示所有的代碼... – Puchacz

+0

雖然有許多工具在那裏..我總是使用微軟提供的工具,除非他們沒有功能我真的需要。我使用.Net SDK中的ILDasm,並會使用外部的,但有名的工具,如.Net Reflector。我知道LinqPad也是知名的,但它的主要目的不是拆卸,因此不會驚訝,如果它沒有給出好的結果。恕我直言,這些着名的工具作者應該知道比運送半烘焙的功能更好..這可能會混淆他們的用戶,而不是幫助。 –

+0

@VikasGupta我很確定LINQPad能夠在目的上顯示IL的簡化視圖。 – svick

5

實際代碼標識將由MethodToken調用的構造函數這些對於每個過載都是唯一的。

你的反彙編器有一個不足的令牌到字符串的轉換,它給你的名字不唯一,也不能被組合。相反,ildasm將令牌轉換爲可以返回到工作組件的完整簽名(使用ilasm)。