在VS2008中如何指定一个64位无符号整数常量0x8000000000000000?

25
我了解到Microsoft的特殊后缀“i64”可用于整数常量。我想对ULONGLONG进行无符号位移操作。
ULONGLONG bigNum64 = 0x800000000000000i64 >> myval; 在普通C语言中,我会使用后缀“U”,例如类似的32位操作应为
ULONG bigNum32 = 0x80000000U >> myval; 我不想使二进制补码符号扩展传播到高位。我想对64位const数字进行无符号位移操作。我认为我的第一个语句将进行有符号右移操作。
我尝试过0x800000000000000i64U0x800000000000000u64,但是得到编译器错误。

3
在我的Visual Studio中,limits.h的语法为0xffffffffffffffffui64,因此0x800000000000000ui64应该可以工作。但是,这是一个较新的版本,所以在Visual Studio 2008中可能无法工作。 - anatolyg
1
@M.M:对于十六进制整数常量,U 可以是 unsigned intunsigned long intunsigned long long intUL 可以是 unsigned long intunsigned long long int。请参阅 C 标准的 6.4.4.1 节(或 C++ 标准的 2.14.3 节)。鉴于此,我认为 OP 的示例没有错误或需要修改。 - Cornstalks
这是一个较旧的问题。在C++11中,我认为不需要任何后缀。只有无符号64位整数类型才能容纳该常量。无符号长长型至少必须是64位,而这是编译器应尝试十六进制格式文字的类型列表之一。http://en.cppreference.com/w/cpp/language/integer_literal - Adrian McCarthy
2个回答

38

您可以使用后缀ull来表示一个unsigned long long整型字面量,这是指定long long至少为64位的标准方式(C99和C++0x)。


1
编译器警告“-Wlong-long”可以在编译器命令行或代码中使用“#pragma GCC diagnostic ignored“ -Wlong-long”来禁用。这里有一个很好的例子,可以暂时为代码部分禁用它。 - Bob Stein
@BobStein-VisiBone:如果您在C99模式或更高版本中编译,则与long long相关的任何内容都不会收到警告。 - AnT stands with Russia
@AnT 我在AtmelStudio中使用了-std=gnu99,这是编译C99模式的方式。我收到了warning: use of C99 long long integer constant [-Wlong-long]警告。因为它是32位MCU,我想要谨慎地使用64位数学运算,所以我默认启用了该警告。 - Bob Stein
@BobStein-VisiBone:这真的是以C99模式编译的“正确方式”吗?你提供的答案似乎并没有说普通的-std=c99不能工作。如果我错了,请纠正我,但对我来说,-std=gnu99听起来像是那个回答者的个人偏好(带有某种神圣战争的态度,因为他甚至都没提到-std=c99)。 - AnT stands with Russia
@AnT 哇,也许情节比我想象的要复杂。你是说在真正的 C99 模式下,没有办法启用对 LL 后缀的警告? -Wlong-long 没有作用吗?还是只是默认关闭了。 - Bob Stein
@BobStein-VisiBone:如果您希望启用它们,应该有一种方法,即使用显式的“-Wlong-long”。但是我希望默认情况下它们是关闭的。 - AnT stands with Russia

14

有趣的是,实际上你不需要在十六进制常量中添加任何后缀才能得到正确的处理。C标准的第6.4.4.1节和C++标准的第2.14.3节包含以下表格:

Suffix       | Decimal Constant       | Octal or Hexadecimal Constant
-------------+------------------------+------------------------------
none         | int                    | int
             | long int               | unsigned int
             | long long int          | long int
             |                        | unsigned long int
             |                        | long long int
             |                        | unsigned long long int
-------------+------------------------+------------------------------
u or U       | unsigned int           | unsigned int
             | unsigned long int      | unsigned long int
             | unsigned long long int | unsigned long long int
-------------+------------------------+------------------------------
l or L       | long int               | long int
             | long long int          | unsigned long int
             |                        | long long int
             |                        | unsigned long long int
-------------+------------------------+------------------------------
Both u or U  | unsigned long int      | unsigned long int
and l or L   | unsigned long long int | unsigned long long int
-------------+------------------------+------------------------------
ll or LL     | long long int          | long long int
             |                        | unsigned long long int
-------------+------------------------+------------------------------
Both u or U  | unsigned long long int | unsigned long long int
and ll or LL |                        |

这个表格告诉我们一个整数常量将会有什么类型。整数常量的类型将是第一个它的值适合的类型。

这意味着编译器将遍历以下类型,以确定十六进制常量 0x800000000000000 的类型(它没有后缀,因此使用“none”行,而且它是十六进制常量,所以使用“Hexadecimal Constant”列),并使用可以存储该值的第一个类型*:

  1. int:不行,32位带符号整数无法存储此值。
  2. unsigned int:不行,32位无符号整数无法存储此值。
  3. long int:不行,32位带符号整数无法存储此值。
  4. unsigned long int:不行,32位无符号整数无法存储此值。
  5. long long int:不行,64位带符号整数无法存储此值。
  6. unsigned long long int:是,64位无符号整数可以存储此值。由于这是可以完全存储该值的第一个类型,因此这就是整数常量的类型。

因此,回答您的问题“如何编写和使用值0x800000000000000并确保编译器不会将高位视为符号位?” :只需编写 unsigned long long value = 0x800000000000000

如果你想对这个值做一些位运算,你可以直接进行操作(例如,只需编写0x800000000000000 >> myval)。你可以确信它不会被视为溢出的有符号整数,并且你的右移不会进行任何符号扩展,因为它是一个正数。

* 我假设 int 是 32 位,long 是 32 位,long long 是 64 位。请注意,您的编译器可能对这些类型使用不同的位大小,这可能会改变最终结果(尽管过程仍然相同)。


你需要小心。如果你想声明一个没有后缀的 unsigned long long,那么你会遇到麻烦:考虑 printf("%lx\n", (0x40000000<<1)>>1); 左/右移应该返回 0x40000000,但如果你运行这个程序,你会得到 0xc0000000。 - Mark Lakata
使用%l将值视为有符号整数或有符号长整数。 如果您对0xc0000000进行2的补码运算,则会得到0x40000000,但这将表示一个负数。Cornstalks是正确的,因为该值被声明为无符号长长整型。如果我将它们声明为有符号整数,则会得到0xc0000000: - NeoH4x0r
我没有完成我的评论: printf("%lx\n", (0x40000000<<1)>>1); // 将值视为有符号而不是无符号。unsigned int a = 0x40000000; unsigned int b = a<<1; unsigned int c = b>>1; printf("%lx\n", c); // 您将获得0x40000000。这是因为没有后缀,使用了none行,并且0x40000000、0x80000000和0xc0000000都可以表示为有符号整数。这正是Cornstalks所说的,他们是正确的。 - NeoH4x0r
此外,printf默认为无符号十六进制输出,但是格式说明符并不等同于常量说明符,后者跟在常量值之后。要使用printf打印64位值,必须使用%lx,并且必须明确定义适当类型的变量——但这仅适用于打印,对算术运算没有影响。需要注意的是,在某些情况下,c++编译器(如g++等)将允许以下操作: unsigned long long int k = 0x800000010000000; unsigned int z =k;z 的值将为0x10000000。 - NeoH4x0r
这个问题是关于Visual Studio的,所以没问题。但我认为在语言中没有任何保证long long不是128位,因此一个有符号的128位整数会导致移位失败。 - Pablo H

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接