提问人:user16217248 提问时间:10/8/2023 最后编辑:user16217248 更新时间:10/9/2023 访问量:90
在 C 中使用有符号整数执行缩小类型转换的最安全、最明确的方法是什么?
What is the safest and most well-defined way to perform narrowing type conversions with signed integers in C?
问:
我读过: 当我在 C 中将 long int 分配给 int 时会发生什么?
int64_t x;
int32_t y;
y = (int32_t)x;
根据答案和 C 标准,如果我尝试将 a 赋值给 a,并且值不合适,则结果是实现定义的,或者会发出实现定义的信号。signed long
signed int
为了解决这种定义不明确的行为,我首先考虑使用无符号类型:
y = (int32_t)(uint32_t)(uint64_t)x;
但这也不行,因为与将较大的整数类型转换为较小的整数类型一样,将无符号整数转换为有符号整数将导致实现定义的值或信号(如果值不合适)。
我想以一种不调用实现定义行为的方式执行缩小转换,例如无符号到有符号或将较大的有符号整数转换为较小的整数,并且只会保留位表示形式(只要符号表示符合预期,这应该会给出非常可预测且定义良好的结果)。有什么方法可以用演员表做到这一点吗?我必须这样使用吗?union
union Cast {
int32_t i32;
int64_t i64;
};
y = ((union Cast){.i64 = x}).i32;
这甚至有效吗(即保证只包含与较低的 32 位完全相同的位模式)?作为最后的手段,必须使用,这会起作用吗?y
x
memcpy()
编辑:事实证明,使用上述方法将不可移植,因为它是否获得上限或下 32 位取决于系统是大端还是小端。但是,如果我使用它:union
union Cast {
int32_t i;
uint32_t u;
};
y = ((union Cast){.u = (uint32_t)x}).i;
那会是便携式的吗?
注意:这个问题是出于好奇而提出的,而且很多时候,出于各种原因,我想安全地截断有符号整数的位。
答:
我想执行缩小转换范围...在某种程度上......将简单地保留位表示(只要符号表示符合预期,这应该会给出非常可预测且定义良好的结果)。
嗯,这是在正确的轨道上,但它缺少一些东西:你没有说预期的标志表示。编写规范的一个专家提示是实际指定您想要的内容。
假设你想要 2 的补码,因为它是最流行的表示方案。等效地,给定一些整数 x,您要转换为有符号的 N 位整数格式,您希望结果 y 与 x 模 2N 一致,其中 −2 N−1 ≤ y < 2 N−1。
这些类型保证使用 2 的补码,并且没有填充位 (C 2018 7.20.1.1 1)。所以,如果你能把 x 的低位变成 ,你就完成了。(我还要注意的是,给定有符号整数类型及其对应的无符号整数类型,标准要求它们对应的值位表示相同的值,在 C 2018 6.2.6.2 2 中。intN_t
intN_t
您不能依赖具有较宽类型的并集,因为 C 标准没有指定存储中字节的顺序,因此您不知道仅从 C 标准中可以识别较宽类型的哪些字节包含其较低的位。我们可以通过将值转换为第一个值来轻松解决这个问题。从无符号类型范围之外的整数到无符号类型的转换完全由 C 标准定义;它包裹模 2N,产生 [0, 2N] 的结果。uintN_t
在这里,我们执行该转换,并在复合文字中使用并集,以 2 的补码重新解释其结果:
(union { uintN_t u; intN_t i; }) {x} .i
虽然问题中没有提到 C++,但我会注意到 C++ 标准没有定义通过联合重新解释。另一种方法是复制字节:
uintN_t u = x;
intN_t i;
memcpy(&i, &u, sizeof i);
这两者都可移植到支持所需固定宽度类型的任何实现。
这也可以使用算术而不是重新解释字节来完成。假设我们正在从一些具有 M 位的更宽类型转换。首先,我们可以强制转换为以获得低位。然后我们可以调整位 N−1 表示的值:。这个答案解释了这个算术。uintN_t
((uintN_t) x ^ ((intM_t) 1 << N-1)) - ((intM_t) 1 << N)
我们还可以使用:
uintN_t u = x;
intN_t i = u - (intM_t) u >> N-1 << N;
这个答案解释了这个算术。
评论
_Static_assert(-1==~0, "Not two's complement");
评论
y = (int32_t) (x & 0xffffffffllu)
i32
i64
int32_t
uint32_t