1u << 8u
是 0x100u
,大于所有uint8_t
值,因此条件从未得到满足。你的“转换”过程实际上只是:
return x;
实际上这是有一定道理的。
您需要更清晰地定义您想要进行转换的内容。C99将从无符号整数类型到有符号整数类型的转换定义如下(§6.3.1.3“有符号和无符号整数”)
当带整数类型的值被转换为除_Bool
之外的其他整数类型时,如果该值可以由新类型表示,则其不变。
...
否则,新类型为有符号类型,而且该值无法在其中表示;结果是具体实现定义的,或者引发具体实现定义的信号。
因此,0
和127
之间的uint8_t
值将被保留,对于大于127
的值的行为是未定义的。许多(但并非所有)实现将简单地将无符号值解释为有符号整数的二进制补码表示。也许你真正想知道的是如何在各个平台上保证这种行为?
如果是这样,您可以使用:
return x < 128 ? x : x - 256;
值
x - 256
是一个
int
类型,保证将
x
解释为补码8位整数。 随后对
int8_t
进行隐式转换可保留此值。
以上假设应理解为
int8_t
,因为不是标准类型。如果不是,则所有赌注都取消了,因为我建议的转换的正确性取决于
int8_t
具有二进制补码表示形式的保证(§7.18.1.1“精确宽度整数类型”)。
如果是某些奇怪的平台特定类型,则可能使用其他表示形式,例如以一补数表示。这会产生不同的可表示值集合,从而使所述转换对于某些输入实现定义(因此不可移植)。
编辑
Alf认为这很“愚蠢”,在任何生产系统上都不需要这样做。 我不同意,但必须承认这是边角情况的边角情况。 他的论点并非没有道理。
然而,他声称这是“低效的”,因此应避免使用,这是毫无根据的。 合理的优化编译器将在不需要的平台上对其进行优化。 例如,在x86_64上使用GCC:
#include <stdint.h>
int8_t alf(uint8_t x) {
return x;
}
int8_t steve(uint8_t x) {
return x < 128 ? x : x - 256;
}
int8_t david(uint8_t x) {
return (x ^ 0x80) - 0x80;
}
使用 -Os 和 -fomit-frame-pointer 编译得到以下结果:
_alf:
0000000000000000 movsbl %dil,%eax
0000000000000004 ret
_steve:
0000000000000005 movsbl %dil,%eax
0000000000000009 ret
_david:
000000000000000a movsbl %dil,%eax
000000000000000e ret
请注意,优化后所有三种实现是相同的。 Clang / LLVM 给出完全相同的结果。 同样,如果我们构建ARM而不是x86:
_alf:
00000000 b240 sxtb r0, r0
00000002 4770 bx lr
_steve:
00000004 b240 sxtb r0, r0
00000006 4770 bx lr
_david:
00000008 b240 sxtb r0, r0
0000000a 4770 bx lr
保护你的实现,使其在没有成本的“常规”情况下免受边缘情况的影响,从来不是“愚蠢”的。
对于认为这样做增加了无谓的复杂性的观点,我要说:哪个更难——写一个注释来解释转换及其存在的原因,还是你的继任者的实习生在10年后尝试调试问题,当一个新的编译器打破了你一直默默依赖的幸运巧合时?维护以下内容真的那么难吗?
int8_t safeConvert(uint8_t x) {
return x < 128 ? x : x - 256;
}
说了那么多,我同意这有点过头了,但我认为我们应该试着直接回答问题。当然,更好的解决方案是C标准明确规定无填充的二进制补码整数类型(即所有intN_t
类型)从无符号类型转换为有符号类型时的行为。
x > 127
,自动转换具有实现定义的行为。 - Stephen Canon