当 x 和 y 都为真时,“x & y”怎么可能为假
本文关键字:amp 怎么可能 | 更新日期: 2023-09-27 18:12:17
上下文:
我正在学习 C#,并且一直在 Pex 上玩得很开心。该站点要求你重新实现秘密算法,方法是在站点中键入代码并检查您的实现和秘密实现之间的输入和输出有何不同。
问题:
无论如何,我陷入了一个名为XAndY的基本代码决斗。
从名字上看,答案似乎很明显:
public static bool Puzzle(bool x, bool y)
{
return x && y;
}
但是,这是不正确的,Pex告诉我,以下输入产生了与秘密实现不同的结果:
输入:
X:真Y:真 (0x02(
输出:
我的实现:真 (0x02(
秘密实现:假
不匹配 您的拼图方法产生了错误的结果。
法典: Puzzle(true, PexSafeHelpers.ByteToBoolean((byte(2((;
在尝试比较不同类型的 true 之后,我意识到 Pex 正在寻找的实现实际上只是使用按位 AND:
return x & y;
问题:
我认为出于语义和短路的原因,您应该使用逻辑&&
来比较布尔值,但无论如何:
- 这是否意味着
x & y
和x && y
对于所有可能的布尔参数,肯定没有相同的输出?(或者可能是佩克斯的越野车? - 这是否意味着您可以在 C# 中区分布尔
true
的不同值?如果是这样,如何?
在我看来,难题是利用 C# 编译器中的一个错误。(该错误也会影响 VB.NET。
在 C# 5.0 规范中,§4.1.8 表示"类型 bool
的可能值是 true
和 false
",§7.11.3 表示operator &(bool x, bool y)
是一个逻辑运算符:
如果
x
和y
都true
,则x & y
的结果是true
的。否则,结果为false
。
这显然违反了true & true
产生false
的规范。这是怎么回事?
在运行时,bool
由 1 字节整数表示。C# 编译器使用 0 表示false
,使用 1 表示true
。为了实现 &
运算符,C# 编译器会在生成的 IL 中发出按位AND
指令。乍一看,这似乎没问题:涉及 0 和 1 的按位AND
运算与涉及 false
和 true
的逻辑AND
运算完全对应。
但是,CLI 规范的 §III.1.1.2 明确允许bool
用 0 或 1 以外的整数表示:
CLI 布尔类型在内存中占用 1 个字节。所有零的位模式表示值为 false。设置了任意一位或多位(类似于非零整数(的位模式表示值为 true。
通过超越 C# 的范围,确实有可能(并且完全合法(创建一个值为 2 的bool
,从而导致&
行为异常。这就是Pex网站正在做的事情。
下面是一个演示:
using System;
using System.Reflection.Emit;
class Program
{
static void Main()
{
DynamicMethod method =
new DynamicMethod("ByteToBoolean", typeof(bool), new[] { typeof(byte) });
ILGenerator il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0); // Load the byte argument...
il.Emit(OpCodes.Ret); // and "cast" it directly to bool.
var byteToBoolean =
(Func<byte, bool>)method.CreateDelegate(typeof(Func<byte, bool>));
bool x = true;
bool y = byteToBoolean(2);
Console.WriteLine(x); // True
Console.WriteLine(y); // True
Console.WriteLine(x && y); // True
Console.WriteLine(x & y); // False (!) because 1 & 2 == 0
Console.WriteLine(y.Equals(false)); // False
Console.WriteLine(y.Equals(true)); // False (!) because 2 != 1
}
}
因此,您的问题的答案是:
- 目前,
x & y
和x && y
可能具有不同的值。但是,此行为违反了 C# 规范。 - 目前,您可以使用
Boolean.Equals
(如上所示(来区分true
值。但是,此行为违反了 CLI 规范Boolean.Equals
。
我注意到这个问题已经提交给了Roslyn编译器组。进一步讨论。
它有以下决议:
这是有效的设计使然,您可以找到文档详细信息 这里: https://github.com/dotnet/roslyn/blob/master/docs/compilers/Boolean%20Representation.md
您可以在此处查看有关此内容的更多详细信息和过去的对话: #24652
所指出的文件指出:
布尔值的表示形式
C# 和 VB 编译器表示真(真(和假(假(布尔值 (布尔(值,单字节值分别为 1 和 0, 并假设他们正在使用的任何布尔值都是 仅限于由这两个基本值表示。这 ECMA 335 CLI 规范允许"真"布尔值为 由任何非零值表示。如果使用具有 0 或 1 以外的基础表示形式,您可能会意外 结果。这可能发生在 C# 中的不安全代码中,或者通过互操作发生 使用允许其他值的语言。为了避免这些意外情况 结果,程序员有责任规范化这样的 传入值。
(所有斜体都是我自己的重点(
C# 中的&
不是按位运算符,假定输入值是Boolean
值。 它过载。 运算符有两个完全独立的实现。 如果输入是布尔值,则为非短路逻辑布尔运算符;如果值为非布尔值,则按位 AND。
在您显示的代码中,输入是一个布尔变量。 它不是数值,也不是解析为布尔值(可能有副作用(或其他任何值的表达式。
当输入是两个布尔变量时,&
和&&
之间的输出永远不会有任何不同。 在这两者之间有任何可观察差异的唯一方法是拥有一个布尔表达式,该表达式比仅将变量解析为其值或某些非布尔输入更复杂。
如果操作数可以是 bool
以外的某种类型,那么为任一运算符提供具有不同结果的类型是非常微不足道的,例如覆盖庄园中true
运算符的超级均值类型与其隐式转换为 bool
不一致:
既然满足难题的实现完全取决于你,你为什么不试试:
public static bool Puzzle(bool x, bool y)
{
return x & !y;
}