无符号长整型 resTestBad = 0xffffffff + 1;使用 %llu 打印 0,但无符号长长 resA = 4294967295 + 1;使用 %llu 打印4294967296

unsigned long long resTestBad = 0xffffffff + 1; print 0 with %llu, but unsigned long long resA = 4294967295 + 1; print 4294967296 with %llu

提问人:Tom 提问时间:8/31/2023 最后编辑:Tom 更新时间:9/1/2023 访问量:142

问:

似乎我在打印 with 的值时发现了一个错误,请参见以下代码:(0xffffffff + 1)%llu0

unsigned long long resTestBad = 0xffffffff + 1;  // number of f is 8
printf("resTestBad is %llu, sizeof(unsigned long long) is %ld\n", resTestBad, sizeof(unsigned long long));

将输出:

resTestBad is 0, sizeof(unsigned long long) is 8

我认为输出是错误的,它应该是输出或说正确的输出:

resTestBad is 4294967296, sizeof(unsigned long long) is 8

unsigned long long resA = 4294967295 + 1; 
printf("resA is %llu, sizeof(unsigned long long) is %zu\n", resA, sizeof(unsigned long long));

将输出:

resA is 4294967296, sizeof(unsigned long long) is 8

但下面的代码没有问题:

unsigned long long resTestAgain = 0xfffffffff + 1;   // number of f is 9
printf("resTestAgain is %llu, sizeof(unsigned long long) is %zu\n", resTestAgain, sizeof(unsigned long long));

它将输出(我认为是对的):

resTestAgain is 68719476736, sizeof(unsigned long long) is 8

我的 .c 文件如下:

#include <stdio.h>
#include <limits.h>

int main(void)
{
    int s = 0xfffe;
    printf("s is %d\n", s);

    unsigned int ww = 0xfffe;
    printf("ww is %d\n", ww);

    unsigned long long resTestOk = 0xfffffffe + 1; 
    printf("resTestOk is %llu, sizeof(unsigned long long) is %zu\n", resTestOk, sizeof(unsigned long long));

    unsigned long long resTestBad = 0xffffffff + 1;  
    printf("resTestBad is %llu, sizeof(unsigned long long) is %zu\n", resTestBad, sizeof(unsigned long long));

    unsigned long long resTestAgain = 0xfffffffff + 1; 
    printf("resTestAgain is %llu, sizeof(unsigned long long) is %zu\n", resTestAgain, sizeof(unsigned long long));

    unsigned long long resA = 4294967295 + 1; 
    printf("resA is %llu, sizeof(unsigned long long) is %zu\n", resA, sizeof(unsigned long long));

    unsigned long long resTestUUU = 0xffffffffffffff + 1; 
    printf("resTestUUU is %llu, sizeof(unsigned long long) is %zu\n", resTestUUU, sizeof(unsigned long long));

    unsigned long long resH = (0xffffffff+1) / 1024 / 1024 / 1024;
    printf("%llu\n", resH);

    unsigned long long resD = (4294967296) / 1024 / 1024 / 1024;
    printf("%llu\n", resD);

    return 0;
}

运行它将输出:

s is 65534
ww is 65534
resTestOk is 4294967295, sizeof(unsigned long long) is 8
resTestBad is 0, sizeof(unsigned long long) is 8
resTestAgain is 68719476736, sizeof(unsigned long long) is 8
resA is 4294967296, sizeof(unsigned long long) is 8
resTestUUU is 72057594037927936, sizeof(unsigned long long) is 8
0
4

也许你不相信,我也不相信,所以我录制了一个视频,为了证明我说。

查看 https://imgur.com/a/RAQVPxS

是CPU的Bug还是VMWare Workstation的Bug?我该如何解决?

更新

对不起,可能是我的问题不清楚,或者说让人感到困惑。

问题的根源是我想测试一台 32 位计算机可以访问多少内存(我知道是 4GB,地址从 0x00000000 到 0xffffffff,总数是 0xffffffff + 1,单位是字节,所以 / 1024 /1024 / 1024 = 4GB),所以我使用下面的代码:(0xffffffff + 1)

unsigned long long resH = (0xffffffff + 1) / 1024 / 1024 / 1024;
printf("%llu\n", resH);

但是它输出的数字,那不是我想要的。除输出外,I 为 4。所以我也使用下面的代码:0

unsigned long long resQ = (4294967295 + 1) / 1024 / 1024 / 1024;
printf("%llu\n", resQ);

它输出 4 我除了什么。所以我想找出导致结果不同的原因。

然后我使用以下代码:

unsigned long long resTestBad = 0xffffffff + 1; 
printf("resTestBad is %llu, sizeof(unsigned long long) is %zu\n", resTestBad, sizeof(unsigned long long));

它输出:

resTestBad is 0, sizeof(unsigned long long) is 8

这不是我的例外,所以我测试了以下代码:

unsigned long long resTestAgain = 0xfffffffff + 1; 
printf("resTestAgain is %llu, sizeof(unsigned long long) is %zu\n", resTestAgain, sizeof(unsigned long long));

它输出:

resTestAgain is 68719476736, sizeof(unsigned long long) is 8

如下代码所示:

unsigned long long resA = 4294967295 + 1; 
printf("resA is %llu, sizeof(unsigned long long) is %zu\n", resA, sizeof(unsigned long long));

它输出:

resA is 4294967296, sizeof(unsigned long long) is 8

通过查看答案,现在我明白为什么 0xffffffff + 1 输出 0。

但是现在我无法理解 0xffffffff 和 4294967295 的值大小是一样的,为什么0xffffffff无符号 int 类型(占用 32 位),但是4294967295可能是长或其他类型(占用 64 位)。

c 整数 printf 文本 整数溢出

评论

4赞 Ry- 8/31/2023
0xffffffffULL + 1ULL
3赞 Gerhardh 8/31/2023
旁注:结果类型为 的正确格式说明符是size_tsizeof%zu
1赞 Support Ukraine 8/31/2023
unsigned long long resTestAgain = 0xfffffffff + 1; // number of f is 8不。。。。再数一遍
3赞 Gerhardh 8/31/2023
%llu可以。但不适合unsigned long long%ldsizeof
3赞 Support Ukraine 8/31/2023
@Tom 你在左手边写什么类型并不重要。用于 Right-Hand-Side 的类型完全独立于 Left-Hand-Side。只有当分配/初始化完成时,即左手边类型才重要。就你而言,为时已晚......右侧已经达到结果零。=

答:

3赞 Ry- #1

正如 @rici 对另一个问题的回答(关于 C++,但相同的规则适用于 C)中所解释的那样,无符号类型是十六进制整数文字的候选者。 适合 32 位 或 ,因此其中之一就是它的类型。然后 转换为 加法,溢出。最后,将 转换为 成为 的初始值。0xffffffffunsigned intunsigned longint1unsigned long/intunsigned long/int0unsigned long longresTestBad

用于从一开始就强制文本。0xffffffffULLunsigned long long

(当您添加另一个 时,该值不再适合 ,因此文本已经具有类型并且它有效。funsigned longlong long

评论

0赞 Tom 8/31/2023
I 声明 unsigned long long resTestBad = 0xffffffff + 1;这意味着占用 64 位,为什么0xffffffff保存到 32 位?
0赞 Andrew Henle 8/31/2023
@Tom为什么0xffffffff保存到 32 位?为什么您认为是 64 位值?回到:如果你赋值给一个,那会让自己成为一个吗?0xfffffffffloat0xfffffffffloat0xfffffffffloat
0赞 Tom 8/31/2023
@AndrewHenle 因为我宣布0xffffffff是无符号的。参见 unsigned long long resTestBad = 0xffffffff + 1;F 的数为 8
2赞 Vlad from Moscow 8/31/2023 #2

对于初学者来说,最好使用转换说明符来输出类型的值,而不是像您所做的那样:%zusize_t%ld

 printf("resTestOk is %llu, sizeof(unsigned long long) is %ld\n", resTestOk, sizeof(unsigned long long));

首先,虽然类型通常表示类型的别名,但根据 C 标准(7.19 通用定义 <stddef.h>)size_tunsigned long

4 用于 size_t 和 ptrdiff_t 的类型不应包含整数 转换排名大于有符号长整型 int 的转换排名,除非 实现支持足够大的对象,使这成为必要。

在您的系统中似乎确实是该类型的别名,并且输出的值可以在有符号类型的对象中表示。size_tunsigned longlong int

现在让我们考虑一下这个声明的例子:

unsigned long long resTestBad = 0xffffffff + 1; 

无符号十六进制常量可以用 类型的对象表示。所以它有类型。因此,在这个表达式中:0xffffffffunsigned intunsigned int

0xffffffff + 1

该类型的对象使用算术(由于通常的算术转换,整数常数也转换为类型)。unsigned int1unsigned int

结果出现溢出,因为结果不能在 类型的对象中表示。表达式的值为 。unsigned int0

以下代码片段中也会出现相同的情况:

unsigned long long resH = (0xffffffff+1) / 1024 / 1024 / 1024;
printf("%llu\n", resH);

其中,该类型的表达式为该类型的对象生成溢出并生成 。0xffffffff+1unsigned int0

至于这个声明:

unsigned long long resTestAgain = 0xfffffffff + 1; 

则十六进制常量不能在 unsigned int 类型的对象中表示(前提是该对象不大于 )。如果等于 或 如果它等于但等于 (在你的程序中它确实等于 ),那么常量可以表示在 类型的对象中。或。0xfffffffffsizeof( unsigned int )4sizeof( long int )84sizeof( long long int )88long intlong long int

所以在这个表达中:

0xfffffffff + 1

对 或 类型的对象使用算术。表达式的结果也可以在该类型的对象中表示。long intlong long int

至于这个代码片段:

unsigned long long resA = 4294967295 + 1; 
printf("resA is %llu, sizeof(unsigned long long) is %ld\n", resA, sizeof(unsigned long long));

则常数大于 等于 的值。因此,它的类型 if 大于 or 否则。表达式的结果可以将该类型的 n 个对象表示为正值。4294967295INT_MAX2147483647long intsizeof( long int )sizeof( int )long long int4294967295 + 1

请注意以下 C 标准(6.4.4.1 整数常量)中关于整数常量类型的引用

5 整数常量的类型是相应的 可以在其中表示其值的列表。

                            Octal or Hexadecimal
Suffix     Decimal Constant            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
================================================================
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
=================================================================

评论

0赞 Tom 8/31/2023
I 声明 unsigned long long resTestBad = 0xffffffff + 1;这意味着占用 64 位,为什么0xffffffff保存到 32 位?
0赞 Support Ukraine 8/31/2023
@Tom 因为 C 标准是这么说的。阅读标准的“6.4.4.1 整数常数”。正如我已经告诉过你的那样:无论你在 LHS 上拥有什么类型,对 RHS 上的类型都没有任何意义。这也是为什么不给你float f = 5/2;2.5
1赞 Support Ukraine 8/31/2023
@Tom我告诉过你好几次了。您在“请参阅我的答案”的左侧写哪种类型并不重要。=
1赞 Peter Cordes 9/1/2023
“一般”可以意味着某件事在任何地方都是真实的,或者几乎在任何地方都是真实的。该标准的这句话不仅仅是一些在“普通”系统上不会出现的晦涩细节,它适用于广泛使用的平台 Windows x64。就像我说的,“通常”或“经常”是这句话的更好选择,以避免暗示这是一个好的假设。OP 只打印带有 的小值,因此我们无法判断它是在查看寄存器的低 32 位还是整个寄存器。例如,我希望在 Windows x64 中具有相同的输出。size_t = unsigned longsize_t%ld
1赞 Peter Cordes 9/1/2023
哦,对了,所以绝对不匹配。它恰好适用于非大数,其中相同的位可以被解释为有符号正数。无论如何,不管你回答的其余部分怎么说,我都建议改变“一般”这个词,因为这通常不是真的。稍后纠正可能具有误导性的陈述不如首先避免给某些读者留下错误的印象。不是每个人都能读懂每个答案的每一个字。我并不是说你的答案在技术上是错误的,我是在建议我认为是一种改进%ld
3赞 Support Ukraine 8/31/2023 #3

此答案假定 int 为 32 位(这是最常见的大小)

是CPU的Bug还是VMWare Workstation的Bug?

不,这不是一个错误。这就是 C 标准所说的必须完成的方式。

做的时候

unsigned long long resTestBad = 0xffffffff + 1;

第一步是计算

0xffffffff + 1;

为此,编译器需要确保两端的操作数具有相同的类型。因此,首先需要弄清楚类型。+

0xffffffff 

是十进制的值4294967295不能存储在 32 位有符号值中。但是,它可以存储在 32 位无符号值中,并在源代码中以十六进制写入。(正如 @Lundin 的回答所指出的,在源代码中只会选择有符号类型,这与十六进制文字不考虑可能性不同。因此,它将是 64 位有符号类型,或者取决于是否是 32 位的 64。您可以通过打印 vs. 自行检查。4294967295unsigned intlonglong longlongsizeof(4294967295)sizeof(0xffffffff)

1具有类型,可以隐式转换为 ,因此编译器选择 32 位无符号进行计算。intunsigned int

因此,计算按以下方式完成:

(unsigned int)0xffffffff + (unsigned int)1;

这给最终结果零提供了(明确定义的)溢出。

所以

unsigned long long resTestBad = 0xffffffff + 1;

真的是一样的

unsigned long long resTestBad = (unsigned int)0;

请注意,左侧的类型对右侧的计算完全没有影响。不管你写 or or ,右手边的计算都是一样的。它将永远是:=unsigned long long resTestBadfloat resTestBaddouble resTestBad

SomeType resTestBad = (unsigned int)0;

顺便说一句:试试这个:

float f = 5/2;

你会因为你在左手边而得到吗?不,除法仍然是整数除法,因此结果是 。2.5float(int)2

当您使用 9 而不是 8 时,该值不能再以 32 位类型保存,因此编译器会选择 64 位类型进行计算。因此,不会出现溢出。ff

我该如何解决?

通过强制以 64 位完成右侧计算。例如:

0xffffffffULL + 1

评论

0赞 Tom 8/31/2023
我看到书名“C编程语言”,你说“C标准”,在那本书里是吗?
0赞 Support Ukraine 8/31/2023
C 标准是描述 C 如何工作的文档。它具有 C 编译器必须遵循的所有规则。
0赞 Support Ukraine 8/31/2023
@Tom 可以免费找到该标准的草案: iso-9899.info/n1570.html 阅读 6.4.4.1 和 6.3.1.8
1赞 Peter Cordes 9/1/2023
或者@SupportUkraine应该修复这个答案以避免暗示 AND 与 C 中的整数文字常量相同;它们有不同的类型。 是 8,是 4,在主流实现中是 32 位,下一个更大的整数大小是 64 位。或者我可以编辑...0xffffffff4294967295sizeof(4294967295)sizeof(0xffffffff)int
1赞 Tom 9/1/2023
@PeterCordes 谢谢你的解释。也就是说,在使用数字时,最好指定其类型,例如或或或或或或(unsigned long long) 0xffffffff + 10xffffffffULL + 14294967295ULL + 1(unsigned long long) 0xffffffff0xffffffffULL4294967295ULL
3赞 Lundin 8/31/2023 #4
  • C 语言中的一切都有一个类型,包括这些东西:它们被称为整数常量1
  • 通常,整数常量的类型为 。或者,除非它太大而无法放入 ,否则它有类型,或者如果仍然不够大,则 。在所有这些情况下,它都是有符号类型。intintlonglong long
  • 十六进制(和八进制)整数常量是特殊的。如果一个十六进制整数常数不能容纳在 中,它将变成 ,或者如果它也不能容纳在十六进制整数常数中,则 、 则以此类推。intunsigned intlongunsigned long

所以在 32 位系统上,然后会得到类型,并且会得到类型。两者都不是足够大的类型来存储结果,因此我们已经在这里找到了错误。int0xffffffffunsigned int1int0xffffffff + 1

现在碰巧的是,如果您有一个 type 操作数和一个 signed 操作数,则隐式转换(通常的算术转换)会将 signed 类型转换为无符号类型。详细信息:隐式类型升级规则unsigned intint

因此,两个操作数都结束,加法的结果也将是 。的结果在 C 中定义明确,在 32 位系统上,我们将得到一个环绕,结果是 。unsigned intunsigned int(unsigned int)0xffffffff + (unsigned int)1int0

从那里开始,您在程序的其余部分使用什么类型并不重要,因为结果已经计算出来了。

结论:在 C 中执行任何操作的类型与给定运算符的操作数有关,与稍后存储结果的赋值的左操作数无关

出于同样的原因,如果您在纸上写下 1+1=2,然后将该纸存储在文件夹中,纸上的方程式不会根据您存储的文件夹类型而神奇地改变 - 它已经执行了。

评论

0赞 Tom 8/31/2023
在您的回答中,“十六进制(和八进制)整数常数很特殊。如果一个十六进制整数常量不能放在一个 int 中,它就会变成无符号的 int,或者如果它也不能放在一个中,那么 long 就这样,依此类推。谢谢你的解释。作为“So on a 32 bit int system”,我使用的是 64 位计算机。
0赞 Lundin 8/31/2023
@Tom是的,它应该说“32 位 int system”,在现实世界中意味着任何 32 位或 64 位 CPU。8 位或 16 位 CPU 使用 16 位 int。
0赞 Tom 8/31/2023
@Lundin我从来没有听说过“8 位或 16 位 CPU 使用 16 位 int.”。谢谢你的解释。
0赞 Tom 8/31/2023
结果和不同吗?(unsigned int)0xffffffff + (unsigned int)14294967295 + 1
1赞 Peter Cordes 9/1/2023
由于我很好奇(并且由于公认的答案忘记了区分十六进制和十进制文字),我做了一个演示(godbolt.org/z/7sab5csrK),展示了在为 x86-64 编译时如何具有有符号的 64 位类型但具有无符号的 32 位类型。(我使用 C++ 作为其关键字,从右侧推断变量的类型。C 使用相同的规则。42949672950xffffffffauto