【问题标题】:When is the integer width restricted?什么时候限制整数宽度?
【发布时间】:2015-04-08 01:52:42
【问题描述】:

我在编写程序时遇到问题,该程序要求用户输入值 N,然后输出结果 5^N。问题是如果用户输入了一个大于 (2^31)-1 的数字,他们的数字就会溢出,程序会给他们一个错误的答案。

我所做的是将用户给定的整数值分配给一个无符号长整数值。这样,如果程序可以检查是否输入了大于 (2^31)-1 的数字,因为 unsigned long 可以容纳的正整数数量比有符号 int 可以容纳的数量多得多。这样做会导致程序正常运行,因为我可以检查是否发生溢出。

但是我的问题是:当用户在原始有符号整数变量中输入大于 2^31 - 1 的值时(这会导致它溢出),为什么我可以将这个溢出的值分配给未签名并得到“正确”的数字是什么?仅在尝试对该号码进行操作时才会发生溢出吗?内存是否存储实际数字(不受位宽限制)?

 int endCount;
unsigned long endCountUn;  /* power N */

/* Read value of N */
printf("This program will compute 5^N; enter N: ");
scanf("%d", &endCount);

/*
User's value put into unsigned long. Helps with detecting overflow.
*/
endCountUn = endCount;

if ( (endCount < 0) && (endCountUn < 2147483648) )
{
    printf("The operation is undefined for negative integers\n");
}

else if ( endCountUn > 2147483647 )
{
    printf("The value exceeds the supported numerical range\n");
}

【问题讨论】:

  • 在 C 中,整数不会“溢出”。您可以将其视为模运算,其中某些运算的结果总是用“模 2 ^(位)”处理。就像附加到您编写的表达式的不可见部分一样。有符号整数通常用 2 的补码表示。你的第一次测试如果基本上测试你的数字的符号位是否为 1(如果它被解释为有符号值,则表示负数)。因此,您只需查看 2 的补码是如何工作的即可了解发生了什么。
  • @user2225104:无符号整数不会溢出;它们以 2^N 为模,其中 N 是位数。另一方面,当有符号整数溢出时,您会触发未定义的行为,并且任何事情都可能发生(包括通过从正到负包装来工作的代码)。未定义的行为是未定义的。
  • 请注意,scanf() 在被要求处理太大而无法放入整数类型的数字时,其行为是未定义的。如果在 32 位有符号类型中输入大于 2^31-1 的值,则得到的结果是未定义的。通常会发生这种情况,但从形式上讲,行为是未定义的。 (ISO 9899:2011 §7.21.6.2 fscanf 函数¶10 说 '……或者如果转换的结果无法在对象中表示,则行为未定义。'
  • @JonathanLeffler 所有“未定义”行为都是正确的。只是实际上与我们的世界,在我们的宇宙中不相关,还没有人编写过“来自地狱的编译器”。没有人生成额外的汇编指令只是为了说明那些未定义的用例可能发生“任何事情”。因此,出于解释的目的,假设负整数“下溢”到最大的正整数就足够了,因为您不会找到没有 2 补码的系统,也不会找到“地狱的编译器”做些别的事,只是为了证明你的观点。

标签: c integer 64-bit overflow 32-bit


【解决方案1】:
if ( (endCount < 0) && (endCountUn < 2147483648) )

这个 if 的块对同时满足两个条件的数 n 执行。 endCount,是一个(32 位)有符号整数,用 2 的补码表示。 因此,表达式的第一项选择设置了高位的所有 n:
范围 1:[0x80000000...0xFFFFFFFF]
第二项选择范围内的所有 n
范围2:[0x00000000..0x7FFFFFFF]。
在这两个范围内的 n 的集合是空集。 所以,你上面的 if 的主体永远不会被执行。

第二个 if(else if 部分)要求范围 1 内的值。

因此,您的测试代码可以简化为:

if( endCount < 0 ) { printf("Value out of range"); }

因为无论用户输入什么(负数或 Range 1 中的值),endCount 将始终为负数。

最后,如果您只想允许无符号整数值,为什么首先使用scanf("%d",endCount)
你可以改写:

uint32_t endCount = 0; // unsigned int is machine dependent...better use stdint.h...
...
scanf("%u", &endCount);

用于产生第二个 printf 的语句的输出...

This program will compute 5^N; enter N: 2147483648
The value exceeds the supported numerical range

用于产生第一个 printf 的语句的输出...

This program will compute 5^N; enter N: -10
The value exceeds the supported numerical range

请查看此程序在您的系统上为此输出什么...

uint32_t endCount;

/* Read value of N */
printf("This program will compute 5^N; enter N: ");
scanf("%u", &endCount);

printf("entered value = %d (as signed)\n",endCount);
printf("entered value = %u (as unsigned)\n",endCount);

在我的系统上:

This program will compute 5^N; enter N: -10
entered value = -10 (as signed)
entered value = 4294967286 (as unsigned)

【讨论】:

  • 感谢您的回答。我不想只允许无符号整数,因为那样我将无法检查用户是否输入了负整数(除非我弄错了)。我对这个程序的整个问题是,我无法真正判断用户是否输入了大于 2^31-1 的数字,因为它会翻转为负整数。如果我没有进行这个检查,程序会一直认为他们输入了一个负整数,而他们真的输入了一个非常大的正整数。另外,请注意,endCount 和 endCountUn 不是同一个变量。
  • 另外,奇怪的是这个程序完全按照它应该运行的方式运行(至少在我的编译器中)。每当您输入负整数或大于 2^31-1 的整数时,都会执行正确的 if 语句。
  • 现在我很好奇你使用的是哪个系统和哪个编译器:)
猜你喜欢
  • 2013-03-11
  • 2019-10-13
  • 2020-01-14
  • 1970-01-01
  • 1970-01-01
  • 2012-03-03
  • 1970-01-01
  • 1970-01-01
  • 2014-04-29
相关资源
最近更新 更多