【问题标题】:Feedback on analysis of code example (secure coding)代码示例分析反馈(安全编码)
【发布时间】:2011-07-07 09:01:26
【问题描述】:

我有一段不确定的作业代码。我有信心知道答案,但我只想与社区再次核对,以防有什么我忘记了。标题基本上是安全编码,问题只是为了解释结果。

int main() {
   unsigned int i = 1;
   unsigned int c = 1;
   while (i > 0) {
     i = i*2;
     c++;
   }
   printf("%d\n", c);
   return 0;
}

我的理由是这样的:

乍一看,您可以想象代码将永远运行,因为它已初始化为正值并不断增加。这当然是错误的,因为最终值会变得如此之大,以至于会导致整数溢出。这反过来也不完全正确,因为最终它将通过使最后一位为 1 来强制对变量“i”进行签名,因此将其视为负数,从而终止循环。所以它不是写入未分配的内存并因此导致整数溢出,而是违反数据类型并因此导致循环终止。

我很确定这是原因,但我只是想仔细检查一下。有意见吗?

【问题讨论】:

  • 不完全——unsigned int 永远不能接受有符号值。您可能会发现尝试编译和运行代码很有启发性:codepad.org/VUjjtvbJ

标签: c loops types integer-overflow


【解决方案1】:

在signed integer 的情况下,最左边的位设置符号,因此该类型元素可以具有的值范围是-2147483648 到2147483647。要从正值获得负值,您可以对所有位进行位反转,反之亦然。

在unsigned integer 的情况下,最左边的位用于存储额外的值。所以我这个类型元素可以具有的值范围是 0 到 4294967295。

在二进制形式中,当i=1; 而你只做i = i*2; 那么值时,你可以拥有的值是:

1 // 1 in base 10
10 // 2 in base 10
100 // 4 in base 10
1000
10000
100000
1000000
10000000
100000000
1000000000
10000000000
100000000000
1000000000000
10000000000000
100000000000000
1000000000000000
10000000000000000
100000000000000000
1000000000000000000
10000000000000000000
100000000000000000000
1000000000000000000000
10000000000000000000000
100000000000000000000000
1000000000000000000000000
10000000000000000000000000
100000000000000000000000000
1000000000000000000000000000
10000000000000000000000000000
100000000000000000000000000000
1000000000000000000000000000000 // 1073741824 in base 10
10000000000000000000000000000000 // 2147483648 in base 10 

现在,如果你有一个循环,如:while (i > 0) { 和 i 的行为如前所述,那么它将处于无限循环中,因为它永远不会为 0。会发生溢出,但你不会停止程序- 它仍然会运行。

如果变量 i 已签名(默认),那么您将得到输出 c=32 作为整数 10000000000000000000000000000000 计算结果为 -2147483648,即 < 0。但是,在这种情况下,您不知道输出。

既然是功课,那么老师自然选择了一个问题,您无法通过运行提供的代码得出任何结论。我假设,这是因为,你想让你知道底层类型是如何存储的,以及有符号和无符号类型之间有什么区别。

另外一个事实是,Java 没有无符号原语或不可变数字类。在某些情况下,没有它们可能会很痛苦。这无疑是一个有用的关键字。

【讨论】:

    【解决方案2】:

    i 始终未签名。在某些时候它会溢出(或者如果这样更清楚的话,它会回绕)并恰好变为 0,它永远不会是负数。

    【讨论】:

    • 否;无符号值不会溢出。
    • 无符号值不会溢出,但它们会环绕。有符号值会溢出,导致未定义的行为。
    【解决方案3】:

    无符号值不会溢出;事实上,它们是保证并定义为环绕的。

    【讨论】:

    • 这如何回答 OP 的问题?
    • 他想知道他的理论是否正确。这就是问题所在。由于上述原因,答案是“不”。
    • 我必须同意stackoverflow.com/users/641980/daniel-dubois 在这一点上-“整数溢出”和“整数换行”在语义上是相同的,无论是签名还是未签名。奇怪的是,维基百科的文章与您声称的完全相反,说无符号和有符号“溢出”,但只有有符号整数可以“环绕”(定义为两个正数相加产生一个负数)。老实说,我没有看到区分的意义。
    • @Chadwick:当有符号整数值根本不环绕时,我看不出溢出和环绕是一回事。在某些平台上,您可能会看到 看起来 是环绕的,这纯粹是因为位是如何布局的;这与无符号值形成对比,无符号值被定义并保证在达到最大值时回绕为零,这不是偶然的,而是设计的。它们不会溢出。
    【解决方案4】:

    处理无符号值时不会溢出。所有计算均以 (UINT_MAX + 1) 为模。

    所以,发生的情况是,通过反复乘以 2 的值最终会回绕为零......并且您的循环停止

    为简单起见,假设无符号为 4 位宽。

    1 * 2 = 2 ==> 0b0010
    2 * 2 = 4 ==> 0b0100
    4 * 2 = 8 ==> 0b1000
    8 * 2 = 0 ==> 0b0000
    

    【讨论】:

      【解决方案5】:

      由于 i 是无符号整数 (0 - 255),由于二进制舍入,在 8 次迭代后 i 将等于 0。

      【讨论】:

      • 当然,需要注意的是这是一个 8 位系统。
      【解决方案6】:

      关于溢出你是对的,但是 unsigned int 是无符号的是有原因的(没有符号位因此是无符号的,unsigned int 从 0 开始)因此它将溢出到 0,如果它是无符号的,它会溢出为负数(例如,在 32 位机器上为 2**31)

      【讨论】:

      • 否;无符号值不会溢出。
      【解决方案7】:

      不,unsigned int 永远不会与零作为有符号数进行比较。循环结束的唯一机会是变量正好为零。

      后者肯定会发生一次,因为您在每次迭代中乘以 2(偶数),这会导致从右侧插入零位(最不重要的位),因此经过一定次数的迭代后所有非零位会被“移出数”而归零。

      【讨论】:

      • 我明白了,毕竟会导致整数溢出。这说得通;在某些时候,该值会很大,它无法容纳在 4 个字节内,并且在某些时候,前 4 个字节中的任何位都不会为 1。我猜如果你只是增加值'i'而不是乘以它,循环将永远不会终止?
      • @KaiserJohaan:当变量增加超过最大可能值时,它将终止。如果您有 32 位 int,您甚至可以对其进行测试 - 它会在合理的时间内发生。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-27
      • 1970-01-01
      • 2022-12-04
      • 1970-01-01
      • 2014-12-17
      相关资源
      最近更新 更多