符合标准的编译器是否可以破坏uint32_t - > int16_t - > int32_t转换?

时间:2012-02-09 23:16:14

标签: c integer-overflow

最近,我们在一些旧代码中发现了奇怪的行为。这段代码已经工作了很长时间,但在某些平台(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的强制转换将无效。在这种情况下,编译器将代码转换为简单的移动是否合法?

4 个答案:

答案 0 :(得分:9)

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

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

在整数转换上,C表示:

  

(C99,6.3.1.3p3)“否则,新类型已签名且值无法在其中表示;结果是实现定义的,或者引发实现定义的信号。”

此案例中gcc的内容记录在此处:

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

  

“为了转换为宽度为N的类型,该值以2 ^ N的模数减少到该类型的范围内;没有信号被提升”

答案 1 :(得分:2)

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

例如,对于实现而言,将超出范围的值转换为int16_t仅保留该值的低15位并始终将符号位设置为是完全合法的。因此,它会将您的sign_extend16()函数解释为return val & 0x7fff;

但是,实现无法解释您的函数,使其只返回val不变 - 实现定义的转换为int16_t必须在某处产生值int16_t的范围,因此最终结果必须位于[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位的符号扩展。

答案 2 :(得分:0)

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

#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;
}

使用带有-O1 x86-64的gcc 4.5.3生成以下输出:

.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

答案 3 :(得分:-1)

使用union:

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;
}