我知道无符号和有符号整数只是根据二进制补码底层位的不同表示形式。也就是说,以下是我的观察——
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 有一个令人讨厌且易于检测的错误,该错误会影响许多将负数转换为无符号并返回的代码,例如对于比特黑客。
将负数限制为零需要所有 ISA 上的额外指令,因为 int16 到 uint16 需要零。 (C 的 GNU 方言早在 C23 之前就保证了 2 的补码有符号整数。GCC 不支持 和 非 2 的补码目标。)
我尝试使用
进行编译以强制防止下溢-ftrapv
这里没有有符号整数溢出。 顺便说一句,即使转换到有符号整数类型也不是 UB,只要源也是整数(例如不是 FP),尽管超出范围的结果可能仅在较旧的 C 版本中由实现定义.
C++23 更改为需要 2 的补码,并简化了规则,因此即使转换为有符号整数模数也会减少 2 的幂。C23 也需要/保证 2 的补码,但我还没有检查转换为有符号整数。 希望是一样的; C 和 C++ 在有意义时尝试保持彼此同步。
使用的系统类型在这里并不重要,因为该代码是完全可移植的。 (虽然这是针对 Cortex M 的,所以它是小端字节序,具有 32 位 2 的补码。)
具体来说,这是 C 要求发生的事情:
int16_t a = -42;
赋值期间会发生从 32 位 -42
到 int
的隐式转换。 int16_t
可以很好地适合 -42
,因此可以保留该值。
int16_t
。赋值期间会发生隐式转换
1)。这种转换是定义明确且可移植的,我们最终得到十进制值
uint16_t b = a;
。
比 65494
uint16_t
uint16_t
-42
的原始二进制表示形式(即 int16_t
)转换为 0xFFD6
的十进制值表示形式。
uint16_t
打印这些数字,严格来说这是未定义的行为。实际上,将会有一个“默认参数提升”到
%d
,并且由于在这种情况下结果可表示为 int
,因此有用的编译器将分别为您提供值 int
和 -42
。而不是 65494
,这应该分别是
%d
和 PRIi16
,来自 PRIu16
。