【发布时间】:2012-10-20 11:14:58
【问题描述】:
我想定义一个函数,它接受 unsigned int 作为参数并返回一个 int 同余模 UINT_MAX+1 到参数。
第一次尝试可能如下所示:
int unsigned_to_signed(unsigned n)
{
return static_cast<int>(n);
}
但正如任何语言律师所知,对于大于 INT_MAX 的值,从无符号转换为有符号是实现定义的。
我想实现这一点,以便 (a) 它仅依赖于规范规定的行为; (b) 它可以在任何现代机器上编译成无操作并优化编译器。
至于奇怪的机器......如果没有有符号的整数与无符号整数模UINT_MAX + 1一致,假设我想抛出一个异常。如果有多个(我不确定这是否可能),假设我想要最大的一个。
好的,第二次尝试:
int unsigned_to_signed(unsigned n)
{
int int_n = static_cast<int>(n);
if (n == static_cast<unsigned>(int_n))
return int_n;
// else do something long and complicated
}
当我不在典型的二进制补码系统上时,我不太关心效率,因为在我看来这不太可能。如果我的代码成为 2050 年无处不在的符号-幅度系统的瓶颈,那么我敢打赌,届时有人可以解决这个问题并对其进行优化。
现在,第二次尝试非常接近我想要的。尽管转换为int 是针对某些输入的实现定义的,但标准保证转换回unsigned 以保留模UINT_MAX+1 的值。所以条件确实检查了我想要什么,它在我可能遇到的任何系统上都不会编译。
但是...我仍在转换为int,而没有首先检查它是否会调用实现定义的行为。在 2050 年的某个假设系统上,它可以做谁知道什么。所以假设我想避免这种情况。
问题:我的“第三次尝试”应该是什么样子?
回顾一下,我想:
- 从无符号整数转换为有符号整数
- 保留值 mod UINT_MAX+1
- 仅调用标准强制行为
- 使用优化编译器在典型的二进制补码机器上编译为空操作
[更新]
让我举一个例子来说明为什么这不是一个微不足道的问题。
考虑具有以下属性的假设 C++ 实现:
-
sizeof(int)等于 4 -
sizeof(unsigned)等于 4 -
INT_MAX等于 32767 -
INT_MIN等于 -232 + 32768 -
UINT_MAX等于 232 - 1 -
int上的算术是模 232(在INT_MIN到INT_MAX的范围内) -
std::numeric_limits<int>::is_modulo是真的 - 将 unsigned
n转换为 int 会保留 0 零
在这个假设的实现中,每个unsigned 值都有一个int 值全等(mod UINT_MAX+1)。所以我的问题会很明确。
我声称这个假设的 C++ 实现完全符合 C++98、C++03 和 C++11 规范。我承认我没有记住所有的每一个字……但我相信我已经仔细阅读了相关部分。因此,如果您希望我接受您的回答,您要么必须 (a) 引用排除此假设实现的规范,要么 (b) 正确处理它。
确实,正确答案必须处理标准允许的每一个假设实现。根据定义,这就是“仅调用标准强制行为”的含义。
顺便提一下,std::numeric_limits<int>::is_modulo 在这里完全没用,原因有很多。一方面,它可以是true,即使无符号到有符号的强制转换不适用于大的无符号值。另一方面,它可以是true,即使在一个补码或符号幅度系统上,如果算术只是对整个整数范围取模。等等。如果你的答案依赖于is_modulo,那就错了。
[更新 2]
hvd's answer 教会了我一些东西:我对整数的假设 C++ 实现不为现代 C 所允许。C99 和 C11 标准对有符号整数的表示非常具体;实际上,它们只允许补码、补码和符号幅度(第 6.2.6.2 节第 (2) 节;)。
但 C++ 不是 C。事实证明,这个事实是我问题的核心。
最初的 C++98 标准基于更老的 C89,它说(第 3.1.2.5 节):
对于每个有符号整数类型,都有一个对应的(但 不同的)无符号整数类型(用关键字指定 无符号),它使用相同的存储量(包括符号 信息)并具有相同的对齐要求。的范围 有符号整数类型的非负值是 对应的无符号整数类型,以及 每种类型的相同值都是相同的。
C89 没有说明只有一个符号位或只允许二进制补码/一个补码/符号幅度。
C++98 标准几乎一字不差地采用了这种语言(第 3.9.1 节第 (3) 节):
对于每一种有符号整数类型,都存在一个对应的 (但不同)无符号整数类型:“
unsigned char”、“unsigned short int”、“unsigned int”和“unsigned long int”,每个 它占用相同的存储量并具有相同的对齐方式 要求(3.9)作为对应的有符号整数类型;那 也就是说,每个 有符号整数 类型都具有与 其对应的无符号整数类型。非负范围 有符号整数类型的值是相应的子范围 无符号整数类型,以及每个的值表示 对应的有符号/无符号类型应相同。
C++03 标准使用与 C++11 基本相同的语言。
据我所知,没有标准 C++ 规范将其有符号整数表示限制为任何 C 规范。并且没有任何东西要求单个符号位或任何类似的东西。它只是说非负有符号整数必须是相应无符号的子范围。
所以,我再次声明 INT_MAX=32767 和 INT_MIN=-232+32768 是允许的。如果您的答案假设不是这样,那么除非您引用 C++ 标准证明我错了,否则它是不正确的。
【问题讨论】:
-
@SteveJessop:实际上,在这种情况下,我确切地说明了我想要的内容:“如果没有有符号 int 与无符号整数模 UINT_MAX+1 一致,假设我想抛出一个异常。”也就是说,如果它存在,我想要“正确的”签名 int。如果它不存在 - 可能发生在例如填充位或补码表示 - 我想检测并处理它以用于特定的演员调用。
-
抱歉,不知道我是怎么错过的。
-
顺便说一句,我认为在您假设的棘手实现中
int至少需要 33 位来表示它。我知道这只是一个脚注,所以你可以说它是非规范性的,但我认为 C++11 中的脚注 49 是 true (因为它是标准中使用的术语的定义) 并且它不与规范性文本中明确说明的任何内容相矛盾。因此,所有负值都必须由设置了最高位的位模式表示,因此您不能将它们中的2^32 - 32768塞进 32 位。并不是说您的论点以任何方式依赖于int的大小。 -
关于您在 hvd 答案中的编辑,我认为您误解了注释 49。您说禁止使用符号大小,但事实并非如此。您已将其读为:“由连续位表示的值是相加的,从 1 开始,并且(乘以 2 的连续积分幂,可能最高位置的位除外)”。我认为应该阅读“由连续位表示的值(是相加的,从 1 开始,乘以 2 的连续积分幂),可能除了最高位置的位”。也就是说,如果设置了高位,则所有赌注都关闭。
-
@SteveJessop:您的解释可能是正确的。如果是这样,它确实排除了我的假设......但它也引入了真正大量的可能性,使得这个问题非常难以回答。对我来说,这实际上看起来像是规范中的一个错误。 (显然,C 委员会是这么想的,并在 C99 中彻底修复了它。我想知道为什么 C++11 没有采用他们的方法?)
标签: c++ casting integer language-lawyer integer-overflow