无符号和有符号整型以及printf

3

我知道我正在给有符号整数赋一个超出它所能处理的值。此外,我应该使用%d表示有符号数,%u表示无符号数。同样地,我不应该给无符号数赋负值。但是,如果我进行这样的赋值并像下面这样使用printf,我会得到下面所示的结果。

我的理解是,在每种情况下,将数字转换为其二进制补码表示,对于-14294967295是相同的。这就是为什么对于有符号数,%u会忽略左侧最高位的-ve标志并打印4294967295。当使用%d表示有符号整数时,它使用最左侧的位作为-ve标志,并打印-1。同样地,%u用于无符号打印无符号值,但%d使其将数字视为有符号数,因此打印-1。正确吗?

signed int si = 4294967295;
unsigned int  ui = 4294967295;

printf("si = u=%u d=%d\n", si, si);
printf("ui = u=%u d=%d\n", ui, ui);

输出:

si = u=4294967295 d=-1
ui = u=4294967295 d=-1

signed int si = -1;
unsigned int  ui = -1;

printf("si = u=%u d=%d\n", si, si);
printf("ui = u=%u d=%d\n", ui, ui);

输出:

si = u=4294967295 d=-1
ui = u=4294967295 d=-1

1
请注意,正如我在回答该问题中所解释的那样-1转换为无符号将始终是该类型的最大无符号值...因此将-1分配给无符号值始终是良好定义的行为。 - Shafik Yaghmour
但是仅仅将负整数值重新解释为“无符号”整数值不是明确定义的。就像重新解释一个过大的“无符号”整数值为一个“有符号”整数值一样。 - Deduplicator
你可能会发现 "std::numeric_limits" 很有用。将类型从 uint32_t 改为 uint64_t 会更易读。例如,std::numeric_limits<int32_t>::max() 可以变成 std::numeric_limits<int64_t>::max()。 - 2785528
2个回答

5
这就是为什么在有符号打印中,使用%u忽略负的最左位打印4294967295。当使用%d打印有符号整数时,它使用最左边的位作为负标志并打印-1。
对于无符号情况,“最左边”或最高有效位不被忽略,并且不是负数;相反,它具有2 ^ 31的位值。
在负数情况下,符号位不是标志;相反,它是一个具有-2 ^ 31位值的位。
在两种情况下,整数的值等于所有二进制数字(位)设置为1的位置值之和。
以这种方式编码有符号值被称为“two's complement”。这不是唯一可能的编码方式;例如,您所描述的是“符号和大小”,而“ones' complement”是另一种可能性。然而,这些替代编码很少在实践中遇到,尤其是因为two's complement是现代硬件上算术运算的工作方式,除了最复杂的体系结构外。

我发现通过查看此情况的1字节等效项更容易形象化。 当8位设置时,作为无符号位模式,其值通常为255。 作为2s补码有符号值,底部7位的值为127,但最高位的值为-128,从而产生有符号值-1。 2、4和8字节值也发生了类似的情况:全1位模式作为有符号整数的值为-1,这只是更容易勾勒出和掌握8位的位模式... - Razzle
@Razzle 确实更容易,但那不是问题的答案。如果您认为它有价值,可以随意发布自己的答案(很可能会有价值)。 - Clifford

2
这里有几件事情需要注意,首先使用错误的格式说明符来调用printf是未定义的行为,这意味着程序的结果是不可预测的,实际发生的情况取决于许多因素,包括编译器、架构、优化级别等等。
对于有符号/无符号转换,这由各自的标准定义,C和C++都将将一个大于有符号整数类型所能存储的值转换为实现定义的行为,从C++草案标准中可以看出:

如果目标类型是有符号的,则如果该值可以在目标类型(和位域宽度)中表示,则该值保持不变;否则,该值是实现定义的。

例如,gcc选择使用与无符号相同的约定:

对于转换为宽度为N的类型,该值被模2^N减少以使其在类型范围内;不会引发任何信号。

当您在C和C++中将-1赋给无符号值时,结果将始终是该类型的最大无符号值,从C++草案标准中可以看出:
如果目标类型是无符号的,则结果值是与源整数同余的最小无符号整数(模2n,其中n是用于表示无符号类型的位数)。[注意:在二进制补码表示中,此转换是概念性的,如果没有截断,位模式不会发生变化。--注]。
C99的措辞更容易理解:
否则,如果新类型是无符号的,则通过重复加上或减去比新类型中可以表示的最大值多一个的值来转换该值,直到该值在新类型的范围内为止。
因此我们有以下结论:
-1 + (UNSIGNED_MAX + 1)

其结果为UNSIGNED_MAX 至于printf和不正确的格式说明符,我们可以从C99标准草案第7.19.6.1节The fprintf function中看到:

如果转换说明符无效,则行为未定义。 248)如果任何参数与相应的转换说明符类型不正确,则行为是未定义的。

fprintf涵盖了printf关于格式说明符的部分,而C++则在处理printf方面回退到了C语言。

@Deduplicator你是指在printf的上下文中吗? - Shafik Yaghmour
1
@Deduplicator,我添加了引用,使其在“printf”上下文中成为UB,并且无论转换是否在优化下做出错误操作,编译器都可以对UB执行任何操作。 - Shafik Yaghmour
不要忘记可变参数函数的措辞,它表示传递的值是signed还是unsigned整数类型并不重要,只要该值在两者中都可以表示即可。 - Deduplicator

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