CS0019: C#中移位操作符的语法陷阱
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 operators 是这样定义 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.
可以看到,双目移位操作符的两个参数实际上是不一样的,操作符左的参数可以是有/无符号的int
和long
,但操作符右边的则都是 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详细说说