%d 代表无符号整数。

3
我在一个在线编译器中不小心使用了"%d"来打印一个无符号整数。我以为会出现错误,但是我的程序可以成功运行。很高兴我的代码能够正常工作,但是我就是不明白为什么。
#include <stdio.h>

int main() {
    unsigned int x = 1;

    printf( "%d", x);

    return 0;
}

@AndreasWenzel 你好!我已经附上了一张图片,但我不确定这是否被认为是最小可重现示例... - Irene
代码是文本。除非您使用图形编辑器编写它,否则您不需要图像。在提问时,请勿上传代码/数据/错误的图像。如果您在发布之前花些时间参观[tour]并阅读[help]页面了解网站的工作方式,您会发现您在这里的体验要好得多。 - Ken White
@KenWhite 对不起,这是我在Stack Overflow上的第一篇帖子。我已经编辑了我的帖子。 - Irene
3个回答

2

“无符号整数”的值足够小,以至于最高位(MSB)未被设置。如果设置了最高位,printf()将把该值视为“负有符号整数”。

int main() {
    uint32_t x = 0x5;
    uint32_t y = 0xC0000000;

    printf( "%d  %u  %d\n", x, y, y );

    return 0;
}

5  3221225472  -1073741824

你可以看到它们之间的区别。

使用“读取”printf格式说明符并将其与后面参数的数据类型进行匹配的新式编译器,可能无法通过警告报告此类型不匹配,这可能是您需要深入研究的内容。


3
根据ISO C11标准的§7.21.6.1 ¶9,如果参数类型不正确,则行为未定义。%d转换格式说明符需要一个int类型的参数,而不是unsigned int类型。因此,就标准而言,您不能依赖于您答案中指定的行为。然而,我仍会给您点赞,因为我不知道有任何现代平台的行为与您答案中指定的行为不同。 - Andreas Wenzel
2
请注意,%u 是用于 unsigned int 的转换格式说明符,但不一定适用于 uint32_t。为了找到 uint32_t 的转换格式说明符,可使用宏 PRIu32,这是一种可移植的方法。 - Andreas Wenzel
1
实际上,我自己也不确定对于你的答案来说最好的代码是什么。一方面,使用固定宽度整数类型很有用,可以确保设置 MSB。另一方面,不应该在固定宽度整数类型中使用“%u”。 - Andreas Wenzel
жңҖж–№дҫҝзҡ„ж–№жі•жҳҜдҪҝз”Ё#include <inttypes.h>并еҶҷе…Ҙprintf( "%"PRIu32" %"PRIu32" %"PRId32"\n", x, y, y );иҖҢдёҚжҳҜprintf( "%d %u %d\n", x, y, y );гҖӮеңЁintдёә32дҪҚзҡ„е№іеҸ°дёҠпјҢPRIu32еҸҜиғҪдјҡжү©еұ•дёәuпјҢPRId32еҸҜиғҪдјҡжү©еұ•дёәdгҖӮ然иҖҢпјҢжҲ‘дёҚзЎ®е®ҡжҳҜеҗҰжҺЁиҚҗдҪҝз”Ёиҝҷдәӣе®ҸпјҢеӣ дёәиҝҷдјҡдҪҝдҪ зҡ„зӯ”жЎҲжӣҙйҡҫйҳ…иҜ»е’ҢзҗҶи§ЈгҖӮ - Andreas Wenzel
1
@0andriy:是的,你说得对。根据我在GCC上的测试,-Wformat-signedness确实会在你使用%d来表示unsigned int时提供警告。似乎这个警告默认情况下是不启用的,即使你使用了-Wall -Wextra参数。 - Andreas Wenzel
显示剩余5条评论

0

请参考printf()手册,其中写道:

指定要应用的转换类型的字符。 转换说明符及其含义如下:

d,i

int参数将转换为有符号十进制表示法。 精度(如果有)提供必须出现的最小数字; 如果转换后的值需要更少的数字,则在左侧用零填充。 默认精度为1。 当使用显式精度0打印0时,输出为空。

因此,这意味着即使参数以unsigned表示方式给出,它也将被转换为其signed int表示形式并打印出来,请参见以下代码示例:

#include <stdio.h>

int main(){
    signed int x1 = -2147483648;
    unsigned int x2 = -2147483648;
    unsigned long long x3 = -2147483648;

    printf("signed int x1 = %d\n", x1);
    printf("unsigned int x2 = %d\n", x2);
    printf("signed long long x3 = %d\n", x3);
}

这是输出结果:

signed int x1 = -2147483648
unsigned int x2 = -2147483648
signed long long x3 = -2147483648

这意味着无论打印的变量类型是什么,只要您指定了%d作为格式说明符,该变量将被转换为其在signed int中的表示形式并进行打印。

对于unsigned char的情况,例如:

#include <stdio.h>

int main(){
    unsigned char s = -10;

    printf("s = %d",s);
}

输出为:

s = 246

由于 unsigned char s = -10 的二进制表示为:

1111 0110

当最高位为1时,但当它被转换为有符号整数时,新的表示形式是:

0000 0000 0000 0000 0000 0000 1111 0110

因此,最高有效位不再具有表示数字是正数还是负数的1位。


1
@Fe2O3:这种行为在ISO C11标准的§6.3.1.3 ¶2中已经定义得很清楚了。因此,我认为不会发生比编译器警告更“极端”的情况。 - Andreas Wenzel
printf 不会为 %d 进行任何转换。任何比 int 更窄的参数都将作为参数提升的一部分转换为 int,因此传递给 %dcharunsigned char 将在 printf 看到它们之前(在普通的 C 实现中)被提升为 int。传递给 printffloatlong long 不会被转换为 int,并且 %d 通常不适用于它们。printf 执行的“转换”是将参数值从数字类型转换为字符字符串,例如将 int 中的数字转换为十进制数字字符串。 - Eric Postpischil
根据C标准,将unsigned int传递给%d具有未定义的行为。在C应用程序中,它通常可以“工作”,因为有一个强烈的对应关系,包括ABI如何处理相同等级的有符号和无符号类型。但这并不是由于printf执行的任何转换而“工作”。 - Eric Postpischil

0
9 有符号整数类型的非负值范围是相应无符号整数类型的子范围,并且每种类型中相同值的表示是相同的。 41)相同的表示和对齐要求意味着可以作为函数参数、函数返回值以及联合成员之间的可互换性。 7 对象的存储值只能通过具有以下类型之一的左值表达式来访问... 与对象的有效类型对应的有符号或无符号类型。
看起来,将无符号整数传递给期望有符号整数的位置,或者反之,只要该值在两种类型中都能表示,就不会出现未定义行为。
也许并非巧合,即使在最高警告级别下,目前没有任何现有的编译器警告这种特定的不匹配。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接