当 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;

问题:

我认为出于语义和短路的原因,您应该使用逻辑&&来比较布尔值,但无论如何:

  1. 这是否意味着 x & yx && y 对于所有可能的布尔参数,肯定没有相同的输出?(或者可能是佩克斯的越野车?
  2. 这是否意味着您可以在 C# 中区分布尔true的不同值?如果是这样,如何?

当 x 和 y 都为真时,“x & y”怎么可能为假

在我看来,难题是利用 C# 编译器中的一个错误。(该错误也会影响 VB.NET。

在 C# 5.0 规范中,§4.1.8 表示"类型 bool 的可能值是 truefalse",§7.11.3 表示operator &(bool x, bool y)是一个逻辑运算符:

如果xytrue,则x & y的结果是true的。否则,结果为 false

这显然违反了true & true产生false的规范。这是怎么回事?

在运行时,bool由 1 字节整数表示。C# 编译器使用 0 表示false,使用 1 表示true。为了实现 & 运算符,C# 编译器会在生成的 IL 中发出按AND指令。乍一看,这似乎没问题:涉及 0 和 1 的按位AND运算与涉及 falsetrue 的逻辑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
    }
}

因此,您的问题的答案是:

  1. 目前,x & yx && y 可能具有不同的值。但是,此行为违反了 C# 规范。
  2. 目前,您可以使用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;
}
相关文章: