使用位移计算C中的有符号长最大值

刚刚开始学习C昨天,这将在新的一年里让我发疯…尝试使用位移操作打印不同的int范围。 除了签名的长最大/最小值之外,一切正常。 无法弄清楚为什么(1 << 63) - 1返回-1 ? 但是(1 << 64) -1对于unsigned long long工程很好……

 #include  #include  void print_range() { signed char scmax = (1 << 7) - 1; char c = scmax; // char means signed char! unsigned char uscmax = (1 << 8) - 1; char cmin = -(1 << 7); unsigned char ucmin = 0; printf("signed char max: %d = %d, unsigned char max: %d = %d\n", scmax, SCHAR_MAX, uscmax, UCHAR_MAX); printf("signed char min: %d = %d, unsigned char min: %d\n", cmin, CHAR_MIN, ucmin); // signed int int imax = (1 << 31) - 1; //(2 << 30) - 1; unsigned int uimax = (1 << 32) - 1; int imin = -(1 << 31); //NOTE: %d is for signed char/short/int, %u is for the unsigned formatter. printf("signed int max: %d = %d, unsigned int max: %u = %u\n", imax, INT_MAX, uimax, UINT_MAX); printf("signed int min: %d = %d, unsigned int min = %d\n", imin, INT_MIN, 0); long long lmax = (1 << 63) - 1L; // WHY DOES THIS NOT WORK??? unsigned long long ulmax = (1 << 64) - 1; long long lmin = -(1 << 63); // NEITHER DOES THIS??? printf("signed long max: %lld = %lld, unsigned long max: %llu = %llu\n", lmax, LLONG_MAX, ulmax, ULLONG_MAX); printf("signed long min: %lld = %lld, unsigned long min: %d\n", lmin, LLONG_MIN, 0); } 

表达式计算为int因为两个操作数都是int 。 你需要long long制作它们:

 ((1LL << 63) - 1) (((long long)1 << 63) -1) 

此外,许多架构最多将移位-1类型的大小,因此它仅移位31位(63位),0位移位32位或64位。

(1<<64)-1)与预期的工作方式不同: (1<<64)由于前一段所述而为00-1-1 ,它被转换为long long ,它仍然是-1LL并转换为unsigned long long它导致最大unsigned long long (由于常见体系结构中有signedunsigned数的2补码表示)

请记住,编程语言是一种规范 ,在某些技术报告中用英语编写。 它不是一个软件。 对于C11 ,请参阅n1570

1这样的文字常量不是很long ,而是一个int

要编写一个文字常量long 1,你需要写1L (或者你可能编码(long)1 ….)。 要编写一个文字常量unsigned long long 1,你应该写1ULL (或代码(unsigned long long)1 ,这是一个常量表达式)。

文字常数适合“最小”整数类型,足以表示它。 所以1是一个int ,并且在64位计算机上(实际上,像我的Linux / x86-64一样实现C) 10000000000 (即10 10 )是一个long (因为它不适合int ),因为在这样的计算机int -s有32位, long -s有64位。

请注意, int的大小或范围不是由C99或C11标准精确定义的,并且可能因实现而异。 您可能希望包含标准头并使用类似int32_t类型…

所以1 << 63是一个(int)1左移63位(因为左操作数是一个int ,移位操作在int -s上)。 在我的Linux / x86-64上, int只有32位,因此该操作是一种未定义的行为

您应该非常害怕未定义的行为,请参阅此答案中的参考。 令人遗憾的是,偶尔会有未定义的行为发生,就像你想要的那样(但它仍然是UB)。

查看代码的另一种方法是关注软件的可移植性 。


BTW,习惯用所有警告和调试信息进行编译,例如使用gcc -Wall -Wextra -g如果使用GCC) 。 有时候,编译器很聪明,在这种情况下会警告你。 然后,改进您的代码以删除所有警告。 稍后,使用调试器( gdb )逐步运行代码并理解(通过查询程序在调试器中的状态)发生了什么。


在您的代码中,您有:

  char c = scmax; // char means signed char! 

可悲的是,它更棘手。 你有C的几种变体或方言。在某些变体中, char被签名,在其他变体中, char是无符号的。 你有什么变体是特定于实现的(编译器编写者选择最容易在某些目标体系结构上实现)。 对于GCC,请参阅C语言选项,如-fsigned-char-funsigned-char (您几乎不应该使用它们,当您使用它们时,要非常小心后果;您可能需要重新编译整个C标准库)。

您的代码在多个计数上调用未定义的行为:

  • 左移1 ,一个int值,超过类型中的位数减一,调用未定义的行为;
  • 左移有符号整数任意量,使得结果值超出类型范围调用未定义行为,就像所有其他有符号算术溢出一样。

用移位计算这些最大值是不方便的。 如果您可以假设2s补码且没有填充位,则可以使用按位补码来获取最大无符号值,并将其向右移动一次以获得最大符号值,然后将其否定并减去1以获得最小符号值。

这是更正后的代码:

 #include  #include  int main(void) { // char unsigned char ucmin = 0; unsigned char ucmax = ~ucmin; signed char scmax = ucmax >> 1; signed char scmin = -scmax - 1; char cmax = ((char)(-1)) < 0 ? scmax : ucmax; char cmin = ((char)(-1)) < 0 ? scmin : ucmin; printf("signed char min: %d = %d, signed char max: %d = %d\n", scmin, SCHAR_MIN, scmax, SCHAR_MAX); printf("unsigned char min: %d, unsigned char max: %u = %u\n", ucmin, ucmax, UCHAR_MAX); printf("char min: %d = %d, char max: %d = %d\n", cmin, CHAR_MIN, cmax, CHAR_MAX); // short unsigned short usmin = 0; unsigned short usmax = ~usmin; signed short smax = usmax >> 1; signed short smin = -smax - 1; printf("short min: %d = %d, short max: %d = %d\n", smin, SHRT_MIN, smax, SHRT_MAX); printf("unsigned short min: %d, unsigned sort max: %u = %u\n", usmin, usmax, USHRT_MAX); // int unsigned int umin = 0; unsigned int umax = ~umin; signed int imax = umax >> 1; signed int imin = -imax - 1; printf("int min: %d = %d, int max: %d = %d\n", imin, INT_MIN, imax, INT_MAX); printf("unsigned int min: %u, unsigned int max: %u = %u\n", umin, umax, UINT_MAX); // long int unsigned long ulmin = 0; unsigned long ulmax = ~ulmin; signed long lmax = ulmax >> 1; signed long lmin = -lmax - 1; printf("long int min: %ld = %ld, long int max: %ld = %ld\n", lmin, LONG_MIN, lmax, LONG_MAX); printf("unsigned long int min: %lu, unsigned long int max: %lu = %lu\n", ulmin, ulmax, ULONG_MAX); // long long int unsigned long long ullmin = 0; unsigned long long ullmax = ~ullmin; signed long long llmax = ullmax >> 1; signed long long llmin = -llmax - 1; printf("long long int min: %lld = %lld, long long int max: %lld = %lld\n", llmin, LLONG_MIN, llmax, LLONG_MAX); printf("unsigned long long int min: %llu, unsigned long long int max: %llu = %llu\n", ullmin, ullmax, ULLONG_MAX); return 0; } 

这一行:

 long long lmax = (1 << 63) - 1L; // WHY DOES THIS NOT WORK??? 

不起作用,因为数字是一个int除非另有声明。

AN int (通常)是32位(您的编译器应该通过以下消息告诉您:

 warning: integer overflow in exression [-Woverflow] 

同样地,对于你有问题的另一个表达。

您的编译器没有告诉您有关此问题的事实意味着您的编译没有启用警告。

对于gcc ,至少使用:

 -Wall -Wextra -pedantic 

我也发现这些参数非常有用:

 -Wconversion -std=gnu99