高效的无符号签名转换,避免实现定义的行为

时间:2012-10-31 02:32:11

标签: c++ language-lawyer

我想定义一个以unsigned int为参数的函数,并向参数返回int全等模UINT_MAX + 1。

首次尝试可能如下所示:

int unsigned_to_signed(unsigned n)
{
    return static_cast<int>(n);
}

但正如任何语言律师所知,从无符号转换为大于INT_MAX的已签名值是实现定义的。

我想实现这一点,以便(a)它只依赖于规范规定的行为; (b)它在任何现代机器上编译成无操作并优化编译器。

至于怪异的机器......如果没有签名的int congruent将UINT_MAX + 1模数为unsigned int,那么我想说要抛出异常。如果有多个(我不确定这是否可行),请说我想要最大的那个。

好的,第二次尝试:

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年的一些假设系统中,它可以做谁知道什么。所以,我想说我想避免这种情况。

问题:我的第三次尝试&#34;&#34;看起来像?

总结一下,我想:

  • 从unsigned int转换为signed int
  • 保留值mod UINT_MAX + 1
  • 仅调用标准强制行为
  • 使用优化编译器
  • 在典型的二进制补码机器上编译成无操作

[更新]

让我举一个例子来说明为什么这不是一个微不足道的问题。

考虑具有以下属性的假设C ++实现:

  • sizeof(int)等于4
  • sizeof(unsigned)等于4
  • INT_MAX等于32767
  • INT_MIN等于-2 32 + 32768
  • UINT_MAX等于2 32 - 1
  • int上的算术模2 32 (进入范围INT_MININT_MAX
  • std::numeric_limits<int>::is_modulo是真的
  • 将无符号n转换为int会保留0&lt; = n&lt; = 32767的值并产生否则

在这个假设的实现中,每个int值只有一个unsigned值全等(mod UINT_MAX + 1)。所以我的问题很明确。

我声称这个假设的C ++实现完全符合C ++ 98,C ++ 03和C ++ 11规范。我承认我没有记住他们所有人的每一句话......但我相信我已仔细阅读了相关章节。因此,如果您希望我接受您的答案,您必须(a)引用一个排除此假设实现的规范或(b)正确处理它。

实际上,正确的答案必须处理标准允许的每个假设实施。这就是&#34;只调用标准强制行为&#34;根据定义,就是指。

顺便提一下,请注意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节):

  

对于每个有符号整数类型,都有一个对应的(但是   另外)无符号整数类型(用关键字指定)   unsigned)使用相同数量的存储空间(包括符号)   信息)并具有相同的对齐要求。范围   有符号整数类型的非负值是。的子范围   相应的无符号整数类型,以及表示形式   每种类型的相同值都是相同的。

C89没有说只有一个符号位或只允许二进制补码/补码/符号幅度。

C ++ 98标准几乎逐字采用了这种语言(第3.9.1节(3)):

  

对于每个有符号整数类型,都存在相应的   (但不同)无符号整数类型:&#34; unsigned char&#34;,&#34; unsigned short int&#34;,&#34; {{1每个都是&#34;和&#34; unsigned int&#34;   它占用相同的存储量并具有相同的对齐方式   要求(3.9)作为对应的有符号整数类型;那   是,每个有符号整数类型具有相同的对象表示   其对应的无符号整数类型。非负的范围   有符号整数类型的值是对应的子范围   无符号整数类型,以及每个的值表示   相应的有符号/无符号类型应相同。

C ++ 03标准使用与C ++ 11基本相同的语言。

据我所知,没有标准C ++规范将其有符号整数表示约束到任何C规范。并且没有强制要求单个标志位或类似的任何东西。它只是说非负有符号整数必须是相应的无符号的子范围。

因此,我再次声称允许INT_MAX = 32767且INT_MIN = -2 32 +32768。如果您的答案另有说明,那么除非您引用 C ++ 标准证明我错了,否则它是错误的。

9 个答案:

答案 0 :(得分:62)

扩展user71404的答案:

int f(unsigned x)
{
    if (x <= INT_MAX)
        return static_cast<int>(x);

    if (x >= INT_MIN)
        return static_cast<int>(x - INT_MIN) + INT_MIN;

    throw x; // Or whatever else you like
}

如果x >= INT_MIN(记住促销规则,INT_MIN转换为unsigned),然后x - INT_MIN <= INT_MAX,那么这不会有任何溢出。

如果这不明显,请查看声明&#34;如果x >= -4u,然后x + 4 <= 3。&#34;,请记住INT_MAX将是等于至少-INT_MIN - 1的数学值。

!(x <= INT_MAX)暗示x >= INT_MIN的最常见系统上,优化器应该能够(并且在我的系统上能够)删除第二个检查,确定两个return可以将语句编译为相同的代码,并删除第一个检查。生成的装配清单:

__Z1fj:
LFB6:
    .cfi_startproc
    movl    4(%esp), %eax
    ret
    .cfi_endproc

您问题中的假设实施:

  • INT_MAX等于32767
  • INT_MIN等于-2 32 + 32768

是不可能的,所以不需要特别考虑。 INT_MIN将等于-INT_MAX-INT_MAX - 1。这是从C的整数类型表示(6.2.6.2)开始的,它要求n位为值位,一位为符号位,并且只允许一个陷阱​​表示(不包括表示)因填充位而无效的,即否则将表示负零/ -INT_MAX - 1的那个。 C ++不允许任何超出C允许的整数表示。

更新 :Microsoft的编译器显然没有注意到x > 10x >= 11测试同样的事情。如果x >= INT_MIN替换为x > INT_MIN - 1u,它只能生成所需的代码,它可以检测为x <= INT_MAX的否定(在此平台上)。

[提问者(Nemo)更新,详细阐述我们的讨论]

我现在相信这个答案适用于所有情况,但原因很复杂。我很可能会给这个解决方案奖励,但我希望在任何人关心的情况下捕获所有血腥细节。

让我们从C ++ 11开始,第18.3.3节:

  

表31描述了标题<climits>

     

...

     

内容与标准C库标题<limits.h>相同。

此处,&#34;标准C&#34;表示C99,其规范严格限制了有符号整数的表示。它们就像无符号整数一样,但有一个专用于&#34; sign&#34;以及专用于&#34;填充&#34;的零个或多个位。填充位对整数值没有贡献,符号位仅作为二进制补码,一补码或符号幅度。

由于C ++ 11从C99继承了<climits>宏,因此INT_MIN是-INT_MAX或-INT_MAX-1,并且保证hvd的代码可以正常工作。 (注意,由于填充,INT_MAX可能比UINT_MAX / 2小得多......但是由于signed-&gt; unsigned casts的工作方式,这个答案处理得很好。)

C ++ 03 / C ++ 98比较棘手。它使用相同的措辞从&#34;标准C&#34;继承<climits>,但现在&#34;标准C&#34;指C89 / C90。

所有这些 - C ++ 98,C ++ 03,C89 / C90 - 都有我在问题中给出的措辞,但也包括这个(C ++ 03第3.9.1节第7段): / p>

  

整数类型的表示应使用a定义值   纯二进制计算系统。(44)[例子:这个国际   标准允许2的补码,1的补码和有符号的幅度   整数类型的表示。]

脚注(44)定义&#34;纯二进制计算系统&#34;:

  

使用二进制数字0的整数的位置表示   和1,其中由连续位表示的值是   添加剂,以1开头,并乘以连续积分   2的幂,除了可能是最高位的位。

这个措辞的有趣之处在于它与自身相矛盾,因为纯粹的二进制数字系统的定义&#34;不允许标志/幅度表示!它确实允许高位具有值-2 n-1 (二进制补码)或 - (2 n-1 -1)(补码) 。但是高位会导致符号/幅度没有价值。

无论如何,我的假设实施&#34;不符合&#34;纯二进制&#34;根据这个定义,所以它被排除了。

然而,高位是特殊的这一事实意味着我们可以想象它会贡献任何价值:小的正值,巨大的正值,小的负值或巨大的负值。 (如果符号位可以贡献 - (2 n-1 -1),为什么不 - (2 n-1 -2)?等等。)

所以,让我们想象一个带符号的整数表示,它将一个古怪的值赋给&#34;符号&#34;位。

符号位的小正值将导致int的正范围(可能与unsigned一样大),并且hvd的代码处理就好了。

符号位的巨大正值会导致int的最大值大于unsigned,这是禁止的。

符号位的巨大负值将导致int表示不连续的值范围,并且规范中的其他措辞表示出来。

最后,一个符号位如何贡献一个小的负数?我们可以在&#34;符号位&#34;贡献,比方说,-37到int的值?那么INT_MAX会(比方说)2 31 -1而INT_MIN会是-37?

这将导致一些数字具有两个表示......但是,one-complement将两个表示形式赋予零,并且根据&#34;示例&#34;允许这样做。规范中没有任何地方说零是唯一整数,可能有两个表示。所以我认为规范允许这个新的假设。

实际上,从-1到-INT_MAX-1的任何负值似乎都可以作为&#34;符号位&#34;的值,但不能更小(以免该范围是非连续的)。换句话说,INT_MIN可能是从-INT_MAX-1到-1的任何内容。

现在,猜猜怎么着?对于hvd代码中的第二个转换来避免实现定义的行为,我们只需要x - (unsigned)INT_MIN小于或等于INT_MAX。我们刚刚显示INT_MIN至少为-INT_MAX-1。显然,x最多为UINT_MAX。将负数转换为无符号与添加UINT_MAX+1相同。把它们放在一起:

x - (unsigned)INT_MIN <= INT_MAX

当且仅当

UINT_MAX - (INT_MIN + UINT_MAX + 1) <= INT_MAX
-INT_MIN-1 <= INT_MAX
-INT_MIN <= INT_MAX+1
INT_MIN >= -INT_MAX-1

最后一个是我们刚刚展示的内容,所以即使在这种反常的情况下,代码实际上也能正常工作。

这耗尽了所有可能性,从而结束了这种极端的学术活动。

结论:C89 / C90中有符号整数有一些严重欠指定的行为,这些行为是由C ++ 98 / C ++ 03继承的。它在C99中修复,C ++ 11通过合并来自C99的<limits.h>间接继承了该修复。但即使是C ++ 11也保留了自相矛盾的纯二进制表示法。措词...

答案 1 :(得分:17)

此代码仅依赖于规范要求的行为,因此很容易满足要求(a):

int unsigned_to_signed(unsigned n)
{
  int result = INT_MAX;

  if (n > INT_MAX && n < INT_MIN)
    throw runtime_error("no signed int for this number");

  for (unsigned i = INT_MAX; i != n; --i)
    --result;

  return result;
}

要求(b)并不容易。这将编译为带有gcc 4.6.3(-Os,-O2,-O3)和clang 3.0(-Os,-O,-O2,-O3)的无操作。英特尔12.1.0拒绝对此进行优化。我没有关于Visual C的信息。

答案 2 :(得分:3)

您可以明确地告诉编译器您要执行的操作:

int unsigned_to_signed(unsigned n) {
  if (n > INT_MAX) {
    if (n <= UINT_MAX + INT_MIN) {
      throw "no result";
    }
    return static_cast<int>(n + INT_MIN) - (UINT_MAX + INT_MIN + 1);
  } else {
    return static_cast<int>(n);
  }
}

使用gcc 4.7.2 x86_64-linuxg++ -O -S test.cpp)与

进行编译
_Z18unsigned_to_signedj:
    movl    %edi, %eax
    ret

答案 3 :(得分:2)

如果x是我们的输入......

如果x > INT_MAX,我们希望找到一个常量k0&lt; x - k*INT_MAX INT_MAX&lt; unsigned int k = x / INT_MAX;

这很简单 - unsigned int x2 = x - k*INT_MAX;。然后,让x2

我们现在可以安全地将int投射到int x3 = static_cast<int>(x2);。让UINT_MAX - k * INT_MAX + 1

如果x3,我们现在要从k > 0中减去x > INT_MAX之类的内容。

现在,在2s补充系统上,只要unsigned int k = x / INT_MAX; x -= k*INT_MAX; int r = int(x); r += k*INT_MAX; r -= UINT_MAX+1; ,就可以了解:

UINT_MAX+1

请注意,{C}保证k*INT_MAX为零,转换为int是noop,我们减去x > INT_MAX然后将其添加回#34;相同的值&#34;。所以一个可接受的优化器应该能够擦除所有那些蠢货!

这留下了x > INT_MAX的问题。好吧,我们创建了2个分支,一个分支UINT_MAX,一个没分支。没有进行强制转换的那个,编译器优化为noop。带有...的那个在优化器完成后做了一个noop。智能优化器将两个分支实现为同一个东西,并删除分支。

问题:如果INT_MAX相对于k*INT_MAX <= UINT_MAX+1非常大,则上述情况可能无效。我隐含地假设enum { divisor = UINT_MAX/INT_MAX, remainder = UINT_MAX-divisor*INT_MAX };

我们可能会用一些枚举来攻击这个:

{{1}}

在2s补码系统上可以得到2和1我相信(我们保证这个数学运算吗?这很棘手...),并根据这些做出的逻辑可以很容易地优化非-2s补充系统...

这也打开了例外情况。只有当UINT_MAX远大于(INT_MIN-INT_MAX)时才有可能,所以你可以将你的异常代码放在if块中以某种方式提出这个问题,并且它不会让你在传统系统上减速。

我不确定如何构造那些编译时常量来正确处理它。

答案 4 :(得分:2)

我的钱是使用memcpy。任何体面的编译器都知道要优化它:

#include <stdio.h>
#include <memory.h>
#include <limits.h>

static inline int unsigned_to_signed(unsigned n)
{
    int result;
    memcpy( &result, &n, sizeof(result));
    return result;
}

int main(int argc, const char * argv[])
{
    unsigned int x = UINT_MAX - 1;
    int xx = unsigned_to_signed(x);
    return xx;
}

对我来说(Xcode 8.3.2,Apple LLVM 8.1,-O3)产生:

_main:                                  ## @main
Lfunc_begin0:
    .loc    1 21 0                  ## /Users/Someone/main.c:21:0
    .cfi_startproc
## BB#0:
    pushq    %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    ##DEBUG_VALUE: main:argc <- %EDI
    ##DEBUG_VALUE: main:argv <- %RSI
Ltmp3:
    ##DEBUG_VALUE: main:x <- 2147483646
    ##DEBUG_VALUE: main:xx <- 2147483646
    .loc    1 24 5 prologue_end     ## /Users/Someone/main.c:24:5
    movl    $-2, %eax
    popq    %rbp
    retq
Ltmp4:
Lfunc_end0:
    .cfi_endproc

答案 5 :(得分:2)

P0907: Signed Integers are Two’s Complement和被选为C ++ 20标准的final wording P1236大大简化了问题。

但是,原始答案仅针对unsigned => int解决了该问题。如果我们想将“某些无符号类型”的一般问题解决为其对应的带符号类型,该怎么办?此外,原始答案在引用该标准的各个部分并分析某些极端情况时非常出色,但是它并不能真正帮助我了解它为什么起作用,因此,该答案将为您提供坚实的概念基础。

C ++ 20答案

template<std::unsigned_integral T>
constexpr auto cast_to_signed_integer(T const value) {
    using unsigned_t = std::conditional_t<sizeof(T) <= sizeof(unsigned), unsigned, T>;
    using signed_t = std::make_signed_t<unsigned_t>;
    using result_t = std::make_signed_t<T>;
    using result_limits = std::numeric_limits<result_t>;
    if (value <= result_limits::max()) {
        return static_cast<result_t>(value);
    } else {
        constexpr auto window = static_cast<signed_t>(result_limits::min());
        return static_cast<result_t>( // cast to the type we want the result in
            static_cast<signed_t>( // cast back to signed or we end up with our original value
                static_cast<T>( // cast to unsigned to force modular reduction
                    static_cast<unsigned_t>(value) + // cast to avoid promotion to int
                    static_cast<unsigned_t>(window) // shift values to overlapping range, cast to silence warning
                )
            ) + window // shift values to negative range
        );
    }
}

与已接受的答案相比,它的强制类型转换要多一些,这是为了确保编译器没有任何有符号/无符号不匹配警告,并正确处理整数提升规则。第一个条件很简单:我们知道该值小于或等于最大值,因此适合。第二个条件即使带有注释也有些复杂,因此一些示例可能会有助于理解为什么每个语句都是必需的。

概念基础:数字行

首先,这个window概念是什么?考虑以下数字行:

   |   signed   |
<.........................>
          |  unsigned  |

事实证明,对于二进制补码整数,您可以将任一类型可以到达的数字行的子集划分为三个大小相等的类别:

- => signed only
= => both
+ => unsigned only

<..-------=======+++++++..>

可以通过考虑表示形式轻松证明这一点。无符号整数以0开头,并使用所有位以2的幂为单位增加值。除符号位值得-(2^position)之外,所有其他位的有符号整数完全相同。而不是2^position。这意味着对于所有n - 1位,它们表示相同的值。然后,无符号整数再增加一个普通位,这会使值的总数增加一倍(换句话说,设置该位的值与未设置该位的值一样多)。除了带该位的所有值均为负之外,带符号整数的逻辑相同。

代码中的window变量是这些段中每个段的大小(我们使用负值,以便可以将其表示为有符号整数)。第一个条件处理的情况是我们位于=节中,这意味着我们位于重叠区域中,一个区域中的值可以不变地表示在另一个区域中。如果我们不在该区域内(我们在+区域内),则需要向下跳转一个窗口大小。这使我们处于重叠范围内,这意味着我们可以安全地从有符号转换为无符号,因为值没有变化。但是,我们尚未完成,因为我们已将两个无符号值映射到每个有符号值。因此,我们需要向下移至下一个窗口(-区域),以便再次获得唯一的映射。

现在,这是否按照问题的要求为我们提供了结果一致的UINT_MAX + 1 mod? UINT_MAX + 1等效于2^n,其中n是值表示形式中的位数。我们用于窗口大小的值等于2^(n - 1)(值序列中的最后一个索引比大小小1)。我们将该值减去两次,这意味着我们减去等于2 * 2^(n - 1)的{​​{1}}。在算术mod 2^n中,加减x是无操作的,因此我们没有影响原始值mod x

正确处理整数促销

由于这是一个通用功能,而不仅是2^nint,因此我们还必须关注完整的促销规则。有两种可能有趣的情况:一种是unsigned小于short,另一种是intshort大小相同。

示例:int小于short

如果int小于short(在现代平台上很常见),那么我们也知道int可以放入unsigned short中,这意味着对其进行的任何操作实际上会在int中发生,因此我们明确地转换为提升类型以避免这种情况。我们的最终声明非常抽象,如果我们替换为真实值,则变得更容易理解。对于我们第一个有趣的情况,在不失一般性的情况下,让我们考虑一个16位的int和一个17位的short(新规则仍允许使用,并且必须具有至少7个填充位):

int

求解最大的16位无符号值

constexpr auto window = static_cast<int17_t>(result_limits::min());
return static_cast<int16_t>(
    static_cast<int17_t>(
        static_cast<uint16_t>(
            static_cast<uint17_t>(value) +
            static_cast<uint17_t>(window)
        )
    ) + window
);

简化为

constexpr auto window = int17_t(-32768);
return int16_t(
    int17_t(
        uint16_t(
            uint17_t(65535) +
            uint17_t(window)
        )
    ) + window
);

简化为

return int16_t(
    int17_t(
        uint16_t(
            uint17_t(65535) +
            uint17_t(32768)
        )
    ) +
    int17_t(-32768)
);

简化为

return int16_t(
    int17_t(
        uint16_t(
            uint17_t(98303)
        )
    ) +
    int17_t(-32768)
);

简化为

return int16_t(
    int17_t(
        uint16_t(32767)
    ) +
    int17_t(-32768)
);

我们投入了尽可能多的未签名内容,并获得return int16_t(-1); ,成功!我们可以在这些步骤的每一个步骤中看到,如果我们没有适当地安排每个演员表,将会发生什么不好的事情。

示例:-1short大小相同

如果intshort的大小相同(在现代平台上很少见),则积分促销规则会稍有不同。在这种情况下,int升级为short,而int升级为unsigned short。幸运的是,我们将每个结果显式转换为要用于计算的类型,因此最终不会出现问题。不失一般性,让我们考虑一个16位的unsigned和一个16位的short

int

求解最大的16位无符号值

constexpr auto window = static_cast<int16_t>(result_limits::min());
return static_cast<int16_t>(
    static_cast<int16_t>(
        static_cast<uint16_t>(
            static_cast<uint16_t>(value) +
            static_cast<uint16_t>(window)
        )
    ) + window
);

简化为

return int16_t(
    int16_t(
        uint16_t(
            uint16_t(65535) +
            uint16_t(32768)
        )
    ) +
    int16_t(-32768)
);

简化为

return int16_t(
    int16_t(32767) + int16_t(-32768)
);

我们投入了尽可能多的未签名内容,并成功退回return int16_t(-1);

如果我只关心-1int而又不关心警告(如原始问题)怎么办?

unsigned

实时观看

https://godbolt.org/z/zc3R35

在这里我们看到clang,gcc和icc在constexpr int cast_to_signed_integer(unsigned const value) { using result_limits = std::numeric_limits<int>; if (value <= result_limits::max()) { return static_cast<int>(value); } else { constexpr int window = result_limits::min(); return static_cast<int>(value + window) + window; } } -O2上不生成任何代码,而MSVC在-O3上不生成任何代码,因此该解决方案是最佳的。

答案 6 :(得分:1)

std::numeric_limits<int>::is_modulo是编译时常量。所以你可以用它来进行模板专业化。问题解决了,至少如果编译器与内联一起播放。

#include <limits>
#include <stdexcept>
#include <string>

#ifdef TESTING_SF
    bool const testing_sf = true;
#else
    bool const testing_sf = false;
#endif

// C++ "extensions"
namespace cppx {
    using std::runtime_error;
    using std::string;

    inline bool hopefully( bool const c ) { return c; }
    inline bool throw_x( string const& s ) { throw runtime_error( s ); }

}  // namespace cppx

// C++ "portability perversions"
namespace cppp {
    using cppx::hopefully;
    using cppx::throw_x;
    using std::numeric_limits;

    namespace detail {
        template< bool isTwosComplement >
        int signed_from( unsigned const n )
        {
            if( n <= unsigned( numeric_limits<int>::max() ) )
            {
                return static_cast<int>( n );
            }

            unsigned const u_max = unsigned( -1 );
            unsigned const u_half = u_max/2 + 1;

            if( n == u_half )
            {
                throw_x( "signed_from: unsupported value (negative max)" );
            }

            int const i_quarter = static_cast<int>( u_half/2 );
            int const int_n1 = static_cast<int>( n - u_half );
            int const int_n2 = int_n1 - i_quarter;
            int const int_n3 = int_n2 - i_quarter;

            hopefully( n == static_cast<unsigned>( int_n3 ) )
                || throw_x( "signed_from: range error" );

            return int_n3;
        }

        template<>
        inline int signed_from<true>( unsigned const n )
        {
            return static_cast<int>( n );
        }
    }    // namespace detail

    inline int signed_from( unsigned const n )
    {
        bool const is_modulo = numeric_limits< int >::is_modulo;
        return detail::signed_from< is_modulo && !testing_sf >( n );
    }
}    // namespace cppp

#include <iostream>
using namespace std;
int main()
{
    int const x = cppp::signed_from( -42u );
    wcout << x << endl;
}

<小时/> 编辑:修复代码以避免在非模块化int机器上存在可能的陷阱(只知道存在一个,即Unisys Clearpath的Archaically配置版本)。为简单起见,这是通过不支持值-2 n -1 来完成的,其中 n int值位的数量,在这样的机器上(即在Clearpath上)。实际上,这台机器也不支持这个值(即带有符号和数字或1的补码表示)。

答案 7 :(得分:1)

我认为int类型至少有两个字节,因此INT_MIN和INT_MAX可能会在不同的平台上发生变化。

Fundamental types

≤climits≥ header

答案 8 :(得分:-4)

这完全符合标准,并将在MSVC / gcc上编译为no-op。

int unsigned_to_signed(unsigned int n)
{
    union UltimateCast
    {
        unsigned int In;
        int Out;
    } cast;

    cast.In = n;

    return cast.Out;
}

对于调用代码,如:

volatile unsigned int i = 32167;

int main()
{
    return unsigned_to_signed( i );
}

我们将有这个汇编输出(g ++ -O3 -S):

__Z18unsigned_to_signedj:
    movl    4(%esp), %eax
    ret
_main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    call    ___main
    movl    _i, %eax
    leave
    ret
    .globl  _i
    .data
    .align 4
_i:
    .long   32167

unsigned_to_signed()声明为inline会产生:

_main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    call    ___main
    movl    _i, %eax
    leave
    ret
    .globl  _i
    .data
    .align 4
_i:
    .long   32167

这是非常简洁的代码。