我在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# 编译器生成的代码有很多不必要的垃圾。
以上观点如有不妥之处,欢迎各位大侠指正。