9

最近,我们在一些旧代码中发现了奇怪的行为。这段代码已经工作了很长时间,但在某些平台(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

4 回答 4

9

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

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

关于整数转换 C 说:

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

此处记录了gcc对这种情况的处理:

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

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

于 2012-02-09T23:27:20.960 回答
2

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

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

但是,实现无法解释您的函数,使其简单地返回val不变 - 实现定义的转换int16_t必须导致在 范围内某处的值int16_t,因此最终结果必须位于某处[0, 32767]or [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 位的符号扩展。

于 2012-02-10T03:00:19.113 回答
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;
}

在 x86-64 上使用 gcc 4.5.3 生成以下输出-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
于 2012-02-10T07:27:08.463 回答
-1

使用联合:

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;
}
于 2012-02-10T05:35:50.477 回答