C - 为什么在使用 uint64_t 计数器时 for 循环会卡住,而 while 循环却不会?
C - Why is a for loop getting stuck when using a uint64_t counter, whereas a while loop isn't?
当我使用带有 uint64_t
的 for
循环作为计数器时,即使条件似乎定义明确,它也会永远卡住。
冒犯 MCVE
#include <stdio.h>
#include <inttypes.h>
int main() {
uint64_t i;
for (i = 15; i >= 0; i--) { printf("%"PRIu64" ", i); }
return 0;
}
部分输出
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 18446744073709551615 18446744073709551614 18446744073709551613 18446744073709551612 18446744073709551611 18446744073709551610 18446744073709551609 18446744073709551608 18446744073709551607 18446744073709551606 18446744073709551605 18446744073709551604 18446744073709551603 18446744073709551602 18446744073709551601 18446744073709551600 18446744073709551599 18446744073709551598 18446744073709551597 18446744073709551596 18446744073709551595 18446744073709551594 18446744073709551593 18446744073709551592 18446744073709551591 18446744073709551590 18446744073709551589 18446744073709551588 18446744073709551587 18446744073709551586 18446744073709551585 18446744073709551584 18446744073709551583 18446744073709551582 18446744073709551581 18446744073709551580 18446744073709551579 18446744073709551578 18446744073709551577 18446744073709551576 18446744073709551575 18446744073709551574 18446744073709551573 18446744073709551572 18446744073709551571 18446744073709551570
它似乎忽略了停止条件,所以它翻转了。
然而,将其更改为 "equivalent" while
循环时,一切正常:
正确的 MCVE
#include <stdio.h>
#include <inttypes.h>
int main() {
uint64_t i = 16;
while (i--) { printf("%"PRIu64" ", i); }
return 0;
}
完成输出
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
我是否遗漏了有关在 for
循环中使用 uint64_t
计数器的内容?非常感谢任何帮助!
如果 i
是无符号类型,则条件 i >= 0
始终为真。递减无符号零不会产生负值,但计数器将回绕到给定类型的最大可表示数。
C 表示具有包含下限和不包含上限的范围。例如,N
个元素的数组具有索引 0 到 N - 1
。上限本身不是有效的数组索引。
此约定意味着您在递增之前使用一个值,但在使用它之前对其进行递减。考虑一个简单的堆栈:
stack[nstack++] = x; // push a value
x = stack[--nstack]; // pop a value
同样的逻辑适用于循环:向前移动时,在递增之前使用该值:
for (var i = 0; i < N; i++) { use(i); }
后退时,先减后用:
for (var i = N; i-- > 0; ) { use(i); }
这个循环等同于你的while
。在处理正文之后发生的更新部分在这里是空的。在进入循环之前对值执行检查;循环体具有更新后的值。
这个向后循环对于空的更新部分可能看起来很尴尬,但在其他方面它与向前版本正交:
- 它使用实际边界;不需要从
N - 1
; 开始
- 它同样适用于任意边界,只要它们遵循包含下限和排除上限的约定;
- 测试是纯粹的不等式,而不是greater/less-then-or相等比较。
如果 i
是无符号整数类型,则表达式 i >= 0
始终为真。另一种简单的方法是将其更改为:
uint64_t i;
for (i = 15; i != -1; i--) { ... }
无论 i
是有符号整数还是无符号整数,这都有效。在 uint64_t
的情况下,-1 将首先转换为 0xFFFFFFFFFFFFFFFF
然后与 i
进行比较。
如果想去掉编译警告,改成:
i != (uint64_t)-1
但您需要确保 uint64_t
与 i
的类型完全相同。
当我使用带有 uint64_t
的 for
循环作为计数器时,即使条件似乎定义明确,它也会永远卡住。
冒犯 MCVE
#include <stdio.h>
#include <inttypes.h>
int main() {
uint64_t i;
for (i = 15; i >= 0; i--) { printf("%"PRIu64" ", i); }
return 0;
}
部分输出
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 18446744073709551615 18446744073709551614 18446744073709551613 18446744073709551612 18446744073709551611 18446744073709551610 18446744073709551609 18446744073709551608 18446744073709551607 18446744073709551606 18446744073709551605 18446744073709551604 18446744073709551603 18446744073709551602 18446744073709551601 18446744073709551600 18446744073709551599 18446744073709551598 18446744073709551597 18446744073709551596 18446744073709551595 18446744073709551594 18446744073709551593 18446744073709551592 18446744073709551591 18446744073709551590 18446744073709551589 18446744073709551588 18446744073709551587 18446744073709551586 18446744073709551585 18446744073709551584 18446744073709551583 18446744073709551582 18446744073709551581 18446744073709551580 18446744073709551579 18446744073709551578 18446744073709551577 18446744073709551576 18446744073709551575 18446744073709551574 18446744073709551573 18446744073709551572 18446744073709551571 18446744073709551570
它似乎忽略了停止条件,所以它翻转了。
然而,将其更改为 "equivalent" while
循环时,一切正常:
正确的 MCVE
#include <stdio.h>
#include <inttypes.h>
int main() {
uint64_t i = 16;
while (i--) { printf("%"PRIu64" ", i); }
return 0;
}
完成输出
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
我是否遗漏了有关在 for
循环中使用 uint64_t
计数器的内容?非常感谢任何帮助!
如果 i
是无符号类型,则条件 i >= 0
始终为真。递减无符号零不会产生负值,但计数器将回绕到给定类型的最大可表示数。
C 表示具有包含下限和不包含上限的范围。例如,N
个元素的数组具有索引 0 到 N - 1
。上限本身不是有效的数组索引。
此约定意味着您在递增之前使用一个值,但在使用它之前对其进行递减。考虑一个简单的堆栈:
stack[nstack++] = x; // push a value
x = stack[--nstack]; // pop a value
同样的逻辑适用于循环:向前移动时,在递增之前使用该值:
for (var i = 0; i < N; i++) { use(i); }
后退时,先减后用:
for (var i = N; i-- > 0; ) { use(i); }
这个循环等同于你的while
。在处理正文之后发生的更新部分在这里是空的。在进入循环之前对值执行检查;循环体具有更新后的值。
这个向后循环对于空的更新部分可能看起来很尴尬,但在其他方面它与向前版本正交:
- 它使用实际边界;不需要从
N - 1
; 开始
- 它同样适用于任意边界,只要它们遵循包含下限和排除上限的约定;
- 测试是纯粹的不等式,而不是greater/less-then-or相等比较。
如果 i
是无符号整数类型,则表达式 i >= 0
始终为真。另一种简单的方法是将其更改为:
uint64_t i;
for (i = 15; i != -1; i--) { ... }
无论 i
是有符号整数还是无符号整数,这都有效。在 uint64_t
的情况下,-1 将首先转换为 0xFFFFFFFFFFFFFFFF
然后与 i
进行比较。
如果想去掉编译警告,改成:
i != (uint64_t)-1
但您需要确保 uint64_t
与 i
的类型完全相同。