CS0019: C#中移位操作符的语法陷阱open in new window

Posted on 2004-08-08 20:40 Flier Lu 阅读(45) 评论(0) 编辑 收藏

为了在一个标志字段中保存多种类型的标志,C 语言中定常见模式之一,是先定义一个 XXX_MASK,再定义一个 XXX_SHIFT,然后通过移位操作定义这段位上的标志,如 WinCrypt.h 中定义证书存储位置标志时,将位置标志位放在高16位中:

// Includes flags and location
#define CERT_SYSTEM_STORE_MASK                  0xFFFF0000

// Location of the system store:
#define CERT_SYSTEM_STORE_LOCATION_MASK         0x00FF0000
#define CERT_SYSTEM_STORE_LOCATION_SHIFT        16

//  Registry: HKEY_CURRENT_USER or HKEY_LOCAL_MACHINE
#define CERT_SYSTEM_STORE_CURRENT_USER_ID       1
#define CERT_SYSTEM_STORE_LOCAL_MACHINE_ID      2

// ……

#define CERT_SYSTEM_STORE_CURRENT_USER          
    (CERT_SYSTEM_STORE_CURRENT_USER_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT)
#define CERT_SYSTEM_STORE_LOCAL_MACHINE         
    (CERT_SYSTEM_STORE_LOCAL_MACHINE_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT)

而在 C# 中一般使用以 FlagsAttribute 标记后的 Enum 来模拟类似语义,如

[Flags]
public enum CertSystemStoreFlag : uint
{    
  // Registry: HKEY_CURRENT_USER or HKEY_LOCAL_MACHINE
  CERT_SYSTEM_STORE_CURRENT_USER_ID               = 1,
  CERT_SYSTEM_STORE_LOCAL_MACHINE_ID              = 2,

  // Includes flags and location
  CERT_SYSTEM_STORE_MASK                          = 0xFFFF0000,

  // Set if pvPara points to a CERT_SYSTEM_STORE_RELOCATE_PARA structure
  CERT_SYSTEM_STORE_RELOCATE_FLAG                 = 0x80000000,
  
  CERT_SYSTEM_STORE_LOCATION_MASK                 = 0x00FF0000,    
  CERT_SYSTEM_STORE_LOCATION_SHIFT                = 16,
  
  CERT_SYSTEM_STORE_CURRENT_USER                  = ……
}

FlagsAttribute 标记使得此 Enum 能够以位域(bit field)形式进行操作,既有 Flags 的类型安全特性,又有进行位操作的灵活性。同时还能定义此 Enum 的基本类型,如上面指定的 uint,以最大限度兼容现有 C 代码。

不过这样组合使用 Enum 的多个特性时有一个小小的语法陷阱,不能将 C# 完全等同于 C++ 的语法,例如要这样照搬 C++ 定义语法:

[Flags]
public enum CertSystemStoreFlag : uint
{ 
  CERT_SYSTEM_STORE_CURRENT_USER_ID               = 1,
  
  CERT_SYSTEM_STORE_LOCATION_SHIFT                = 16,
  
  CERT_SYSTEM_STORE_CURRENT_USER = CERT_SYSTEM_STORE_CURRENT_USER_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT,
}

编译时就会获得一个让人困惑的警告消息:

以下为引用:

CS0019: Operator '<<' cannot be applied to operands of type 'uint' and 'uint'

以一个 C++ 背景的程序员角度来看,实在是无法想像为什么移位操作竟然不能对 uint 进行处理,不过在 C# 中这恰恰是语法所要求的。ECMA-334 C# Language Specification 的第 14.8 节 Shift operatorsopen in new window 是这样定义 C# 的移位操作符的:

以下为引用:

The << and >> operators are used to perform bit shifting operations.
  
    shift-expression :
        additive-expression
        shift-expression << additive-expression
        shift-expression >> additive-expression
        
...

The predefined shift operators are listed below.

    2 Shift left:

        int operator <<(int x, int count);  
        uint operator <<(uint x, int count);  
        long operator <<(long x, int count);  
        ulong operator <<(ulong x, int count);  

    3 The << operator shifts x left by a number of bits computed as described below. 4 The high-order bits outside the range of the result type of x are discarded, the remaining bits are shifted left, and the low-order empty bit positions are set to zero. 
    
    5 Shift right: 
    
        int operator >>(int x, int count);  
        uint operator >>(uint x, int count);  
        long operator >>(long x, int count);  
        ulong operator >>(ulong x, int count);  
    
    6 The >> operator shifts x right by a number of bits computed as described below. 7 When x is of type int or long, the low-order bits of x are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero if x is non-negative and set to one if x is negative. 8 When x is of type uint or ulong, the low-order bits of x are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero.        

可以看到,双目移位操作符的两个参数实际上是不一样的,操作符左的参数可以是有/无符号的intlong,但操作符右边的则都是 int。因此上面那个错误的表达式中,移位操作符右边数字应该被显式转换为 int 来符合 C# 的语法要求。

[Flags]
public enum CertSystemStoreFlag : uint
{ 
  CERT_SYSTEM_STORE_CURRENT_USER_ID               = 1,   
  
  CERT_SYSTEM_STORE_LOCATION_SHIFT                = 16,
  
  CERT_SYSTEM_STORE_CURRENT_USER = CERT_SYSTEM_STORE_CURRENT_USER_ID << (int)CERT_SYSTEM_STORE_LOCATION_SHIFT,
}

呵呵,虽然比较别扭,但没办法,谁让 C# 定义得这么严谨呢 😛

btw: 感谢 Junfeng Zhang 帮忙指出问题所在


1楼

2004-08-09 10:02 by 笨笨蜗牛
没有实验过,不过当我对ulong数据移位时好象没发现这样的问题(估计我只是在解决问题的时候看运行结果了)

呵呵。

long型整数的每个字节存放到一个数组元素里???请教高手来讨论一下~~~~!
http://community.csdn.net/Expert/TopicView.asp?id=3251848

2楼 2004-08-09 15:31 by Flier Lu

to 笨笨蜗牛:

我的解决方法,呵呵,有点小题大做了
using System;
using System.Runtime.InteropServices;

class EntryPoint
{
  public static void Main(String[] args)
  {
    byte[] bytes = new byte[8];

    long l1 = 1234567890123456, l2 = 0;

    GCHandle h1 = GCHandle.Alloc(l1, GCHandleType.Pinned);
    Marshal.Copy(h1.AddrOfPinnedObject(), bytes, 0, bytes.Length);
    h1.Free();

    GCHandle h2 = GCHandle.Alloc(l2, GCHandleType.Pinned);
    Marshal.Copy(bytes, 0, h2.AddrOfPinnedObject(), bytes.Length);
    long l3 = (long)h2.Target;
    h2.Free();

    Console.Write("{0} -> {1} -> {2}", l1, l2, l3);

    return;
  }
}

回头我写篇blog详细说说

Contributors: FHL