在 C 中使用有符号整数执行缩小类型转换的最安全且最明确定义的方法是什么?

问题描述 投票:0回答:1

我读过:当我在 C 中将 long int 赋值给 int 时会发生什么?

int64_t x;
int32_t y;

y = (int32_t)x;

根据答案和 C 标准,例如,如果我尝试将

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;

这是否有效(即保证

y
仅包含与
x
的低 32 位完全相同的位模式)?作为最后的手段,必须使用
memcpy()
,这会起作用吗?

编辑:结果使用上面的

union
将不可移植,因为是否获得高位或低位32位取决于系统是大端还是小端。但是,如果我使用这个:

union Cast {
    int32_t i;
    uint32_t u;
};
y = ((union Cast){.u = (uint32_t)x}).i;

那是便携式的吗?

注意: 这个问题是出于好奇而提出的,事实上,很多时候,出于各种原因,我想安全地截断有符号整数的位。

c type-conversion portability signed type-narrowing
1个回答
0
投票

我想执行缩小转换...以一种...将简单地保留位表示(只要符号表示符合预期,这应该给出非常可预测且定义良好的结果)。

嗯,这是在正确的轨道上,但它遗漏了一些东西:你没有说预期的符号表示是什么。编写规范的专家技巧是实际指定您想要的内容。

假设您想要二进制补码,因为它是最流行的表示方案。同样地,给定一些整数 x,您想要将其转换为有符号 N 位整数格式,您希望结果 yx 模 2N 和 −2N− 一致1y< 2N−1.

intN_t
类型保证使用二进制补码并且没有填充位(C 2018 7.20.1.1 1)。因此,如果您可以将 x 的低位转换为
intN_t
,那么您就完成了。 (我还要注意,给定一个有符号整数类型及其相应的无符号整数类型,标准要求它们相应的值位代表相同的值,在 C 2018 6.2.6.2 2 中。)

您不能依赖更宽类型的联合,因为 C 标准没有指定存储中的字节顺序,因此仅根据 C 标准您不知道更宽类型的哪些字节包含其低位。我们可以通过首先将值转换为

uintN_t
来轻松解决这个问题。从超出无符号类型范围的整数到无符号类型的转换完全由 C 标准定义;它包装模 2N,产生 [0, 2N) 的结果。

在这里,我们执行该转换并在复合文字中使用并集,以用二进制补码重新解释其结果:

(union { uintN_t u; intN_t i; }) {x} .i

虽然问题中没有提到 C++,但我会注意到 C++ 标准没有定义通过联合重新解释。另一种方法是复制字节:

uintN_t u = x;
intN_t i;
memcpy(&i, &u, sizeof i);

这两者都可以移植到支持所需固定宽度类型的任何实现。

这也可以使用算术来完成,而不是重新解释字节。假设我们正在从某种具有 M 位的更宽类型进行转换。首先我们可以转换为

uintN_t
以获得低位。然后我们可以调整位N−1:
((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;

该算术在这个答案中进行了解释。

© www.soinside.com 2019 - 2024. All rights reserved.