我在2009年4月19日寫的一篇隨筆“Timus 1037. Memory management”中,使用了如下的一個結構(Structs)來表示“內存塊”:
struct Block
{
public int Id { get; private set; }
public int Time { get; set; }
public Block(int id, int time) : this() { Id = id; Time = time; }
}
在這個結構中,Id 表示“內存塊”的編號,Time 表示該“內存塊”到期時間,它們都是自動實現的屬性(Auto-Implemented Properties)。
下面,就是我們這次的主角 Block.cs 源程序文件:
using System;
namespace Skyiv.Ben.Test
{
struct Block
{
public int Id { get; private set; }
public int Time { get; set; }
public Block(int id) : this() { Id = id; }
}
sealed class Test
{
static void Main()
{
Console.WriteLine(new Block(37).Time);
}
}
}
我們將分別在 Windows 和 Linux 操作系統下編譯這個 C# 源文件。
Windows 操作系統的版本如下所示:
編譯器是:
E:\work> csc –out:block.windows.exe block.cs
Microsoft (R) Visual C# 2008 Compiler version 3.5.30729.1
for Microsoft (R) .NET Framework version 3.5
Copyright (C) Microsoft Corporation. All rights reserved.
E:\work>
Linux 操作系統和編譯器如下所示:
ben@linux-cod2:~/work> cat /etc/issue
Welcome to openSUSE 11.1 - Kernel \r (\l).
ben@linux-cod2:~/work> uname -srvm
Linux 2.6.27.21-0.1-default #1 SMP 2009-03-31 14:50:44 +0200 x86_64
ben@linux-cod2:~/work> mono --version
Mono JIT compiler version 2.4 (tarball Fri Mar 13 15:52:25 UTC 2009)
Copyright (C) 2002-2008 Novell, Inc and Contributors. www.mono-project.com
TLS: __thread
GC: Included Boehm (with typed GC)
SIGSEGV: altstack
Notifications: epoll
Architecture: amd64
Disabled: none
ben@linux-cod2:~/work> gmcs --version
Mono C# compiler version 2.4.0.0
ben@linux-cod2:~/work> gmcs -out:block.mono.exe block.cs
ben@linux-cod2:~/work>
下面就是在這兩個操作系統下分別編譯後的結果:
E:\work>dir
Volume in drive E is Data2
Volume Serial Number is 16BB-989E
Directory of E:\work
2009/05/08 21:21 <dir> .
2009/05/08 21:21 <dir> ..
2009/05/08 20:02 318 block.cs
2009/05/08 20:10 3,584 block.mono.exe
2009/05/08 21:21 4,096 block.windows.exe
3 File(s) 7,998 bytes
2 Dir(s) 61,416,194,048 bytes free
E:\work>
在 Windows 操作系統上編譯後的程序可以在 Linux 操作系統下運行,反之亦然。
在 Windows 操作系統下運行:
E:\work> block.windows.exe
0
E:\work> block.mono.exe
0
E:\work>
在 Linux 操作系統下運行:
ben@linux-cod2:~/work> mono block.windows.exe
0
ben@linux-cod2:~/work> mono block.mono.exe
0
ben@linux-cod2:~/work>
下面,我們用 ildasm 來反匯編這兩個 .exe 文件。
從上圖中可以看出,這兩個 .exe 文件中的內容幾乎是一樣的,除了 Block 結構的 Id 和 Time 屬性用 Microsoft C# 編譯器比用 mono C# 編譯器多了個 instance 修飾符。
下面就是 Block 結構的 Id 屬性(總是先 Microsoft 後 mono,下同):
下面就是 Block 結構的 Id 屬性的 get 方法:
從上圖中可以看出,Microsoft C# 編譯器生成的代碼有很多不必要的 IL 代碼,不好。注意,上述代碼是直接用 csc.exe 編譯的,在編譯時沒有加上 /debug+ 參數,而不是在 Visual Studio 2008 IDE 中編譯的。
而 mono C# 編譯器生成的代碼就非常好,沒有多余的 IL 代碼。
下面就是 Block 結構的 Id 屬性的 set 方法:
這下,Micorsoft 和 mono 生成的代碼又完全一樣,奇怪。
下面就是 Block 結構的構造函數:
從上圖中可以看出,Microsoft 生成的代碼除了有多余的 nop 以外,還多了以下一行:
IL_0001: initobj Skyiv.Ben.Test.Block
這一行代碼,是用來調用 Block 結構的默認(無參的)構造函數,對應下面 C# 源程序代碼:
public Block(int id) : this() { Id = id; }
中的“ : this() ” 。
如果刪除這個“ : this() ” ,用 Microsoft C# 編譯器編譯時就會出錯,如下所示:
E:\work2> csc block.cs
適用於 Microsoft(R) .NET Framework 3.5 版的 Microsoft(R) Visual C# 2008 編譯器 3.5.30729.1 版
版權所有(C) Microsoft Corporation。保留所有權利。
block.cs(9,28): error CS0188: 在給“this”對象的所有字段賦值之前,無法使用該對象
block.cs(9,12): error CS0843:
必須對自動實現的屬性“Skyiv.Ben.Test.Block.Id”的支持字段完全賦值,才能
將控制返回給調用方。請考慮從構造函數初始值設定項中調用默認構造函數。
block.cs(9,12): error CS0843:
必須對自動實現的屬性“Skyiv.Ben.Test.Block.Time”的支持字段完全賦值,才
能將控制返回給調用方。請考慮從構造函數初始值設定項中調用默認構造函數。
E:\work2>
但是,如果用 mono C# 編譯器編譯就可以順利通過。實際上,即便加上這個“ : this() ” ,mono C# 編譯器也完全無視它,也就是說,即使在有“ : this() ” 的情況下,mono C# 編譯器也不會生成調用 Block 結構的默認構造函數的 IL 代碼,它直接忽略了這個“ : this() ”。而且,這樣做也沒有造成什麼不良後果,block.mono.exe 在 Windows 和 Linux 操作系統下都運行良好。
最後,block.cs、block.windows.exe 和 block.mono.exe 這三個文件可以在這裡下載。
實際上,之所以會寫這篇文章,是因為我在做“Timus 1037. Memory management”這道 ACM 題的時候,是在 Ubuntu 9.04 Linux 下使用 MonoDevelop 2.0 寫程序的,如下所示:
從上圖中可以看出,在 Block 結構的構造函數中沒有“ : this() ” ,這在 Linux 下運行得很好。但是,提交到 ACM 網站後,由於該網站是使用 Microsoft Visual C# 2008 версии 3.5.30729.1 編譯器,導致編譯出錯。
這就引起了我比較 Microsoft C# 編譯器和 mono C# 編譯器的興趣,於是就產生了這篇文章。
總結一下,我認為目前的 mono C# 編譯器生成的代碼比較高效,而 Microsoft C# 編譯器生成的代碼有很多不必要的垃圾。
以上觀點如有不妥之處,歡迎各位大俠指正。