我一直在查看我挖出的一些旧的(Reflector)反编译源代码。该DLL最初是使用.NET 2.0从Visual Basic .NET源代码编译的 - 除此之外我还没有关于编译器的信息。
在某些时候发生了一些奇怪的事情。代码中有一个分支没有被跟踪,即使条件应该已经存在。确切地说,这是分支:
[...]
if (item.Found > 0)
{
[...]
现在,有趣的是,如果item.Found为-1
,则输入if
语句的范围。 item.Found
的类型为int
。
为了弄清楚发生了什么,我去了IL代码,发现了这个:
ldloc.3
ldfld int32 Info::Found
ldc.i4.0
cgt.un
stloc.s flag3
ldloc.s flag3
brfalse.s L_0024
显然Reflector在这里错了。正确的反编译代码应该是:
if ((uint)item.Found > (uint)0)
{ ... }
目前为止上下文确定。现在我的问题。
首先,我无法想象有人在编写这段代码; IMO没有一个心智健全的人会区分' -1'和' 0'这种方式 - 这是“发现”中唯一的两个价值观。可以有。
所以,这让我得出结论,编译器做了我不理解的事情。
ceq
或bne_un
- 这是我所期望的并且通常是由C#生成的?答案 0 :(得分:4)
看起来很古怪,但这与以前版本的Visual Basic相关,后者以VB6结束。它有一个非常不同的布尔类型表示a VARIANT_BOOL。由于需要支持遗留代码,这仍然是VB.NET的一个因素。
True的值表示不同,它是-1。 False为0,就像在.NET中一样。
虽然这看起来也是一个非常古怪的选择,但任何其他语言都使用1表示True,有一个非常的理由。它使逻辑和数学And
和Or
运算符之间的区别消失。这是非常好的,程序员不需要学习的另一件事。从大多数C#程序员编写的代码类型来看,这是一个学习障碍非常明显,他们在if()语句中盲目地应用&&
或||
。即使这样做不是一个好主意,由于机器代码中需要短路分支,这些操作员也很昂贵。如果处理器的分支预测很难预测左操作数,那么由于管道停滞,你很容易丢失一堆cpu周期。
很好但不是没有问题,And
和Or
总是评估左右操作数。这有绊倒异常的诀窍,有时你真的需要短路。 VB.NET添加了AndAlso
和OrElse
运算符来解决这个问题。
所以cgt.un
有意义,可以处理.NET布尔值和遗留值。如果True值为-1或1,它并不关心。并不关心变量或表达式实际上是布尔值,在VB.NET中允许使用Option Strict Off。
答案 1 :(得分:3)
作为一个实验,我编译了这个VB代码:
Dim test As Boolean
test = True
Dim x As Integer
x = test
If x Then Console.WriteLine("True")
IL的发布版本是:
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor()
.entrypoint
.maxstack 2
.locals init (
[0] bool test,
[1] int32 x)
L_0000: ldc.i4.1
L_0001: stloc.0
L_0002: ldloc.0
L_0003: ldc.i4.0
L_0004: cgt.un
L_0006: neg
L_0007: stloc.1
L_0008: ldloc.1
L_0009: ldc.i4.0
L_000a: cgt.un
L_000c: brfalse.s L_0018
L_000e: ldstr "True"
L_0013: call void [mscorlib]System.Console::WriteLine(string)
L_0018: ret
请注意使用cgt.un
Reflector对C#的解释是:
bool test = true;
int x = (int) -(test > false);
if (x > 0x0)
{
Console.WriteLine("True");
}
和VB一样:
Dim test As Boolean = True
Dim x As Integer = CInt(-(test > False))
If (x > &H0) Then
Console.WriteLine("True")
End If
因此,我得出结论,生成的代码与VB布尔值转换为数值有关。
答案 2 :(得分:1)
让我们首先考虑有两个可能的值-1
和0
。如果42
在那里结束,那么应该怎么做;这是不可能的(你的陈述中是正确的)还是只是可能的(这个值就像一个variant_bool,其中-1
是正常的真值,但是所有的非零都应该被视为真)这是值得考虑的无论哪种方式。将42
与我们对待-1
的方式相同是有意义的;也就是说,将所有非零视为相同是有意义的。
即使绝对没有其他可能的非零值而不是-1
,它仍然会推广到“测试非零”,这在其他地方是非常常见的情况,所以考虑这个仍然是有意义的“测试非零”案例。如果编译器不知道-1
是唯一可能的非零值(非常可能),则尤其如此。
现在存在的问题是是否直接对值进行分支(使用brfalse
,brtrue
等)或进行布尔运算然后对结果进行分支。通常,C#和VB.NET编译器都会生成一个布尔值,然后在调试版本中对其进行分支:
简单代码:
public void TestBool(bool x)
{
if(x)
throw new ArgumentOutOfRangeException();
}
调试CIL:
nop
ldarg.1
ldc.i4.0
ceq
stloc.0
ldloc.0
brtrue.s NoError
newobj instance void [mscorlib]System.ArgumentOutOfRangeException::.ctor()
throw
NoError:
ret
发布CIL:
ldarg.1
brfalse.s NoError
newobj instance void [mscorlib]System.ArgumentOutOfRangeException::.ctor()
throw
NoError:
ret
在进行分支之前基本上执行x == true
的额外步骤有助于调试。在发布代码中有时会看到类似的效果,但不太常见。
因此,我们在代码中的分支之前进行比较,而不仅仅是分支。
现在还有另一个问题,我们是否应该测试该值是零还是测试该值不为零;或者相当于:
if(x)
DoSomething();
和
if(!x)
{
}
else
DoSomething();
是等效的。
因此可以使用ceq
,后续分支适用于item.Found
为0
的情况。但是如果使用cne
并且随后的分支适用于item.Found
不是0
的情况,则更合理。
但是没有像cne
这样的CIL指令,或任何可以比较测试某些东西是否相等的指令。通常要做“检查不相等”,我们会执行序列ceq
,ldc.i4.0
,ceq
;检查两个值是否相等,然后检查该检查的结果是否为假。
幸运的是,在常见的情况下,我们正在检查的内容不等于0
我们不需要cne
,因为cgt.un
在逻辑上等同于假设cne
} 在这种情况下。当我们想测试某些东西不为零时,这会使cgt.un
成为明显的选择。
因此,虽然IYO“没有一个心智健全的人以这种方式区分'-1'和'0',但这通常是一种非常理智的方式来测试非零。事实上,cgt.un
通常只是一种非零测试。
相关:最有可能的原始源代码是什么?
If item.Found Then
'More stuff
End If
这相当于C#
if(item.Found != 0)
{
//More stuff
}