tags:

views:

207

answers:

5

I have a piece of code, which outputs different results, depending on the C# compiler and the runtime.

The code in question is:

using System;

public class Program {
    public static void Main() {
        Console.WriteLine(string.Compare("alo\0alo\0", "alo\0alo\0\0", false, System.Globalization.CultureInfo.InvariantCulture));
    }
}

The results are:

                    Compiling with mono (gmcs)    Compiling with .Net (csc)
Running with mono                           -1                           -1
Running with .Net                           -1                            0

How can it output different values, when running with the .Net framework?

(BTW, according to http://msdn.microsoft.com/en-us/library/system.string.aspx the output should be 0, so mono's answer is incorrect, but that's unrelated to my question.)

Even the generated IL code is (almost) the same.

Compiling with .Net:

.method public hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       29 (0x1d)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr      bytearray (61 00 6C 00 6F 00 00 00 61 00 6C 00 6F 00 00 00 ) // a.l.o...a.l.o...
  IL_0006:  ldstr      bytearray (61 00 6C 00 6F 00 00 00 61 00 6C 00 6F 00 00 00   // a.l.o...a.l.o...
                                  00 00 ) 
  IL_000b:  ldc.i4.0
  IL_000c:  call       class [mscorlib]System.Globalization.CultureInfo [mscorlib]System.Globalization.CultureInfo::get_InvariantCulture()
  IL_0011:  call       int32 [mscorlib]System.String::Compare(string,
                                                              string,
                                                              bool,
                                                              class [mscorlib]System.Globalization.CultureInfo)
  IL_0016:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_001b:  nop
  IL_001c:  ret
} // end of method Program::Main

Compiling with mono:

.method public hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       27 (0x1b)
  .maxstack  8
  IL_0000:  ldstr      bytearray (61 00 6C 00 6F 00 00 00 61 00 6C 00 6F 00 00 00 ) // a.l.o...a.l.o...
  IL_0005:  ldstr      bytearray (61 00 6C 00 6F 00 00 00 61 00 6C 00 6F 00 00 00   // a.l.o...a.l.o...
                                  00 00 ) 
  IL_000a:  ldc.i4.0
  IL_000b:  call       class [mscorlib]System.Globalization.CultureInfo [mscorlib]System.Globalization.CultureInfo::get_InvariantCulture()
  IL_0010:  call       int32 [mscorlib]System.String::Compare(string,
                                                              string,
                                                              bool,
                                                              class [mscorlib]System.Globalization.CultureInfo)
  IL_0015:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_001a:  ret
} // end of method Program::Main

The only difference is the two extra NOP instructions in the .Net version.

How is it possible? How can the two output values be different?

Also, if anyone has both .Net and mono installed, can you reproduce it?

EDIT: I don't care what the correct result is, and I don't care that mono and .Net produces different results. I'll probably never encounter embedded nulls AND sort them AND the sorting order will be important.

My problem is that the same runtime (.Net 2.0) produces different results, when compiled by different compilers.

EDIT 2: I added a table and tried to clarify the question, it should be easier to understand now.

+2  A: 

My guess is that when you're compiling it with Mono, it's referencing the .NET 2.0 version of mscorlib - whereas when you compile it with VS, it's targeting .NET 4.0.

I may be incorrect about which exact version is being targeted in each case, but that's where I'd look to start with: don't look at the IL for the method, look at the referenced assemblies.

(It may help if you'd say which versions of VS, .NET and Mono you've got installed, btw.)

EDIT: Okay, so if it does the same thing regardless of what version you target, how about running a diff on the results of running ildasm on each version? Compare the whole files, not just the IL for the method call itself.

Jon Skeet
Nope, I tried targeting 2.0 and 3.5, same results.
Hali
I did a dump with ildasm, and actually I found a difference. The VS compiler added a CompilationRelaxationsAttribute to my assembly silently. So I added it explicitly: same results.The only other differences are the order of methods, code size, offsets etc.
Hali
It's getting weirder and weirder...I reassembled the dumps using ilasm, and I got 0s for both dumps. I tried with mono's ilasm too, the output is 0.But if I compile the original C# source with gmcs, I get -1 (running with .Net 2.0).
Hali
+1  A: 

Is this related to string ordering? See this blog entry by Marc

Rohan West
A: 

String comparison is determined by the CultureInfo.CompareInfo. My guess would be that the CultureInfo.CompareInfo is returning different bitmasks on .Net vs. Mono. .Net 3.5 returns 0xff for when I print System.Globalization.CultureInfo.InvariantCulture.CompareInfo. What does it print under Mono? Compare missing bits to System.Globalization.CultureInfo.CompareOptions. If the bitmasks are the same, then there's a bug in either Mono or .Net in how the culture info is interpreted.

With this in mind, I would highly down there's any observable difference in your code, if the disassembly is the same.

Nathan Ernst
+1  A: 

I think this is just another Mono's incompatibility to .NET (especially in System.String::Compare when handling culture info), so please log a report to inform Novell/Mono team. They can feedback and confirm whether it is desired. If this is a bug, you will know at least when it can be fixed.

http://www.mono-project.com/Bugs

Lex Li
A: 

I would guess that nop (No Operation) asm commands are for commands aligning in memory. This gives CPU chance to load code into internal cache and therefor run it faster. It is pretty standard optimization technique and it seems that .NET compiler does it while Mono doesn't care.

Rafal Ziolkowski
IL is independent of CPU architecture. The nops are to add explicit sequence points for the debugger. You won't find them in release builds.
mhutch