当x和y都为真时,“x&y”怎么会变错?

问题描述 投票:19回答:3

语境:

我正在学习C#并且一直在搞乱Pex for fun网站。该站点向您提出重新实施秘密算法的挑战,方法是在站点中键入代码并检查输入和输出在您的实现和秘密实现之间的差异。

问题:

无论如何,我被卡在一个名为XAndY的基本代码决斗上。

从名称来看,答案显而易见:

public static bool Puzzle(bool x, bool y) 
{
    return x && y;
}

但是,这是不正确的,Pex告诉我,以下输入产生的结果与秘密实现不同:

输入:

x:true y:true(0x02)

输出:

我的实现:true(0x02)

秘密实施:假

不匹配你的拼图方法产生了错误的结果。

代码:Puzzle(true,PexSafeHelpers.ByteToBoolean((byte)2));

在尝试比较不同类型的true之后出现了很多困惑之后,我意识到Pex正在寻找的实现实际上只是使用了按位AND:

return x & y;

问题:

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

  1. 这是否意味着x & yx && y对所有可能的bool争论最终没有相同的输出? (或者它可能是Pex中的一些东西吗?)
  2. 这是否意味着您可以区分C#中bool true的不同值?如果是这样,怎么样?
c# boolean bitwise-operators logical-operators pex
3个回答
20
投票

这个难题正在利用我认为是C#编译器中的错误。 (该bug也会影响VB.NET。)

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

如果x & ytrue都是xy的结果是true。否则,结果是false

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

在运行时,bool由1字节整数表示。 C#编译器使用0表示false,使用1表示true。为了实现&运算符,C#编译器在生成的IL中发出按位AND指令。乍一看,这似乎没问题:涉及0和1的按位AND操作完全对应于涉及ANDfalse的逻辑true操作。

但是,CLI specification的§III.1.1.2明确允许bool由0或1以外的整数表示:

CLI布尔类型在内存中占用1个字节。全零的位模式表示值false。设置任何一个或多个位的位模式(类似于非零整数)表示值true。

超越C#的范围,确实可能 - 并且完全合法 - 创建一个bool,其值为2,从而导致&出乎意料地行为。这就是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值。但是,此行为违反了Boolean.Equals的CLI规范。

1
投票

假设输入值是&值,C#中的Boolean不是按位运算符。它超载了。运算符有两个完全独立的实现。如果输入是布尔值,则为非短路逻辑布尔运算符;如果值为非布尔值,则为按位AND。

在您显示的代码中,输入是一个布尔变量。它不是一个数值,它不是一个解析为布尔值(可能有副作用)的表达式,或其他任何东西。

当输入是两个布尔变量时,&&&之间的输出中永远不会有任何不同。在这两者之间存在任何可观察差异的唯一方法是使布尔表达式比仅将变量解析为其值或某些非布尔输入更复杂。

如果操作数可能是除了bool之外的某种类型,那么提供对于任一运算符具有不同结果的类型非常微不足道,例如在庄园中覆盖true运算符的超级均值类型与其隐式转换为bool不一致:


1
投票

我注意到这个问题has been raised到Roslyn编译器组。 Further discussion

它有以下决议:

这是有效的设计,你可以在这里找到详细说明的文件:https://github.com/dotnet/roslyn/blob/master/docs/compilers/Boolean%20Representation.md

您可以在此处查看有关此内容的更多详细信息和过去的对话:#24652

该文件指出:

布尔值的表示

C#和VB编译器分别用单字节值1和0表示true(True)和false(False)bool(布尔)值,并假设它们使用的任何布尔值都限于由这两个表示潜在价值。 ECMA 335 CLI规范允许“true”布尔值由任何非零值表示。如果使用具有0或1之外的基础表示的布尔值,则可能会得到意外结果。这可能发生在C#中的不安全代码中,或者通过与允许其他值的语言进行互操作。为避免出现这些意外结果,程序员有责任规范化此类传入值。

(所有斜体都是我自己的重点)

© www.soinside.com 2019 - 2024. All rights reserved.