我理解无符号整数和有符号整数只是根据二进制补码对底层位的不同表示。也就是说,以下是我的观察——b
是一个非零整数,很好:
// gcc main.c -o main.out && ./main.out
#include <stdio.h>
#include <stdint.h>
void main() {
int16_t a = -42;
uint16_t b = a;
printf("a = %d\n", a); // a = -42
printf("b = %d\n", b); // b = 65494
}
现在,我的一位同事声称有证据表明,在我们的嵌入式软件项目中(到目前为止我无法获得编译器工具链的详细信息),b
可能仅限b = 0
于此!
处理器似乎是 S32G。
问题: GCC 编译器工具链中是否有任何设置,或者是否存在 CPU 架构,使得无符号到有符号值赋值成为限制操作?
我尝试使用-ftrapv
强制编译来防止下溢,但产生的结果与上述相同......
更新:我发现错误在其他地方:中间赋值已转换float
为uint16_t
,这似乎是一个完全不同的问题,可能存在 UB。问题得到了回答。我更像是:
// gcc main.c -o main.out && ./main.out
#include <stdio.h>
#include <stdint.h>
void main() {
float a = -42;
uint16_t b = a;
printf("a = %.1f\n", a); // a = -42.0
printf("b = %d\n", b); // b = ???
}
ISO C 要求此程序打印
65494
;需要从整数类型转换为无符号整数类型,以模数减少到目标类型的值范围。(在这种情况下,添加 2^16 以将其带入 0..65535 范围)。实际上,对于 2 的补码,这仅意味着在必要时截断位模式,或者对窄源进行符号扩展或零扩展。
所以不是,除非 GCC 有一个令人讨厌且易于检测的错误,该错误会影响大量将负数转换为无符号数并转回的代码,例如 bithacks。
将负数限制为零将需要在所有 ISA 上添加一条额外指令,因为 int16 到 uint16 需要零。(C 的 GNU 方言早在 C23 之前就保证了 2 的补码有符号整数。GCC 不支持非 2 的补码目标。)
这里没有有符号整数溢出。顺便说一句,只要源也是整数(例如不是 FP),即使转换为有符号整数类型也不是 UB,尽管超出范围的结果可能仅在旧 C 版本中由实现定义。
C++23 改为要求使用 2 的补码,并简化了规则,因此即使转换为有符号整数模数也会减少 2 的幂。C23 也要求/保证使用 2 的补码,但我还没有检查转换为有符号整数的措辞。希望是一样的;C 和 C++ 会在有意义的时候尝试保持同步。
由于此代码完全可移植,因此所使用的系统类型并不重要。(尽管这是针对 Cortex M 的,因此它是小端字节序,具有 32 位 2 的补码。)
具体来说,这是 C 所要求的:
int16_t a = -42;
-42
分配期间会发生从32 位int
到 的隐式转换int16_t
。-42
可以很好地适应,int16_t
因此值得以保留。uint16_t b = a;
。赋值期间发生隐式转换1)。此转换定义明确且可移植,最终得到十进制值65494
。uint16_t
=65536。uint16_t
。-42
或者如果您愿意,请将中的int16_t
的原始二进制表示形式0xFFD6
转换为 的十进制值表示形式uint16_t
。至于用 打印这些数字
%d
,严格来说,这是未定义的行为。实际上,将有一个“默认参数提升”到int
和 因为结果可表示,如本例所示,有用的编译器将分别为您提供和 的int
值。-42
65494
而不
%d
应该分别是PRIi16
和。PRIu16
inttypes.h
1)C23 6.3.1.3 “否则,如果新类型是无符号的,则通过反复加或减比新类型中可以表示的最大值多一来转换该值,直到该值在新类型的范围内。” 这是这种算术的数学结果,与操作数的类型没有任何与 C 相关的担忧。