【问题标题】:Can a conforming compiler break uint32_t -> int16_t -> int32_t conversions?符合标准的编译器可以破坏 uint32_t -> int16_t -> int32_t 转换吗?
【发布时间】:2012-02-09 23:16:14
【问题描述】:

最近,我们在一些旧代码中发现了奇怪的行为。这段代码已经工作了很长时间,但在某些平台(XBox 360、PowerPC)上破坏了编译器优化。通常,我会怀疑未定义的行为。

代码大致如下:

#include <stdint.h>
uint32_t sign_extend16(uint32_t val)
{
   return (int32_t)(int16_t)val;
}

它是模拟器的一部分,所以有问题的操作应该不会太奇怪。 通常,我希望这仅考虑较低的 16 位并将其符号扩展为 32 位。 显然,这是它多年来的行为。在 x86_64 上,GCC 给了我这个结果:

0000000000000000 <sign_extend16>:
   0:   0f bf c7                movswl %di,%eax
   3:   c3                      retq

但是,根据我对标准的理解,如果无法用有符号类型表示无符号的值,则未定义将无符号转换为有符号。

那么编译器是否可以假设无符号值必须在[0, 32767] 的范围内,因为任何其他值都未定义?在这种情况下,对int16_t 的强制转换和对int32_t 的另一个强制转换将无济于事。在这种情况下,编译器将代码转换为简单的移动是否合法?

【问题讨论】:

  • (int16_t)val 的行为永远不会不确定。如果val 可以表示为int16_t,则其行为是明确定义的,否则行为是实现定义的。
  • @Maister 您在 x86_64 上到底有什么问题? movswl 指令执行符号扩展。当您传递值 32768 时,您的结果是什么?在带有gcc 的32 位/64 位系统上,返回值应为0xFFFF8000
  • 我可能还不够清楚。 x86_64 上的行为是预期的。但是,它在 xbox 360 上并没有按预期运行。
  • @Maister xbox 360上的编译器是什么,参数32768返回什么值?
  • 如果您通过演员 (uint16_t) 明确截断 val 作为第一步,它可能会有所帮助;如果这样做不行,您应该尝试使用类型双关语进行转换 uint16_t -> int16_t...

标签: c integer-overflow


【解决方案1】:

两种整数类型之间的转换绝不是未定义的行为。

但一些整数转换是实现定义的。

关于整数转换 C 说:

(C99, 6.3.1.3p3) "否则,新类型是有符号的,值不能在其中表示;结果要么是实现定义的,要么是产生实现定义的信号。"

gcc 在此案例中的作用在此处记录:

http://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html

"为了转换为宽度为 N 的类型,值以 2^N 为模减少到类型的范围内;不产生信号"

【讨论】:

    【解决方案2】:

    正如ouah 所说,转换超出范围的值会产生实现定义的结果(或允许引发实现定义的信号)。

    例如,对于一个实现来说,将超出范围的值转换为int16_t 只保留值的低 15 位,并且始终将符号位设置为 0,这是完全合法的。因此,它会将您的 sign_extend16() 函数解释为简单的 return val &amp; 0x7fff;

    但是,实现不能解释您的函数,使其简单地返回 val 不变 - 实现定义的到 int16_t 的转换必须导致在 @ 范围内的某个值987654327@,所以最终结果必须在[0, 32767][4294934528, 4294967295] 中的某个位置。

    还要注意,int32_t 的演员表完全是多余的。

    不依赖于实现定义的转换的两个替代方案是(注意val 的参数类型的变化):

    uint32_t se16(uint16_t val)
    {
        return -((uint32_t)val << 1 & 0x10000) | val;
    }
    
    
    uint32_t se16(uint16_t val)
    {
        return (val ^ (uint32_t)32768) - (uint32_t)32768;
    }
    

    ...但不幸的是,gcc 优化器似乎没有注意到这些只是低 16 位的符号扩展。

    【讨论】:

    • ((int32_t)val - 32768) ^ (int32_t)(-32768) 呢?
    • @supercat:是的,这也有效,(val ^ (uint32_t)32768) - (uint32_t)32768 也是如此。不过,我无法让优化器生成单个 movswl
    【解决方案3】:

    我在 cmets 中已经提到的两个版本:

    #include <stdint.h>
    
    uint32_t sign_extend16_a(uint32_t val)
    {
        return (uint32_t)(int16_t)(uint16_t)val;
    }
    
    uint32_t sign_extend16_b(uint32_t val)
    {
        union { uint16_t u; int16_t i; } ui;
        ui.u = (uint16_t)val;
        return (uint32_t)ui.i;
    }
    

    使用 gcc 4.5.3 在 x86-64 上使用 -O1 生成以下输出:

    .globl sign_extend16_a
        .def    sign_extend16_a;    .scl    2;  .type   32; .endef
    sign_extend16_a:
        subq    $8, %rsp
        movswl  %cx, %eax
        addq    $8, %rsp
        ret
    .globl sign_extend16_b
        .def    sign_extend16_b;    .scl    2;  .type   32; .endef
    sign_extend16_b:
        subq    $8, %rsp
        movswl  %cx, %eax
        addq    $8, %rsp
        ret
    

    【讨论】:

      【解决方案4】:

      使用联合:

      uint32_t sign_extend16(uint32_t val){
          union{
              uint32_t a;
              int32_t b;
              int16_t c;
          }o;
          o.a=val;
          o.b=o.c;
          return o.a;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-04-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-10-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多