使用无符号长整型进行符号扩展

5
我们发现一些奇怪的值被产生了,以下是一个小的测试案例。 这段代码输出的是"FFFFFFFFF9A64C2A"。这意味着无符号长整型似乎已经进行了符号扩展。 但是为什么? 下面所有的类型都是无符号的,那么是什么在进行符号扩展? 期望的输出应该是 "F9A64C2A"。
#include <stdio.h>

int main(int argc,char *argv[])
{
    unsigned char a[] = {42,76,166,249};

    unsigned long long ts;
    ts = a[0] | a[1] << 8U | a[2] << 16U | a[3] << 24U;

    printf("%llX\n",ts);


    return 0;

}
3个回答

5
在表达式a[3] << 24U中,a[1]的类型为unsigned char。现在,“整数提升”将其转换为int,因为:

以下内容可在表达式中使用,无论何时都可以使用intunsigned int

[...]

如果int可以表示原始类型的所有值,则将该值转换为int;否则,它将转换为unsigned int

((草案)ISO/IEC 9899:1999,6.3.1.1 2)

请注意,移位运算符(除大多数其他运算符外)不执行“通常的算术转换”,将两个操作数转换为公共类型。但是

结果的类型是左操作数提升后的类型。

(6.5.7 3)

在32位平台上,将249 << 24解释为int,其符号位被设置为1。

只需更改为

ts = a[0] | a[1] << 8 | a[2] << 16 | (unsigned)a[3] << 24;

修复了这个问题(常量后缀U没有影响)。


@user964970:再读一遍。 x << y 的类型与 y 的类型无关。 - Dietrich Epp
@user964970,你没看到吗?“结果的类型是左操作数提升后的类型。” - Shahbaz

1

一些从unsigned char自动转换为int的a[i]值,会产生符号扩展的值。

这符合C草案标准N1570第6.3.1节算术运算对象、子节6.3.1.1布尔、字符和整数的规定,其中部分内容如下:“2.以下内容可在表达式中使用,无论何时都可以使用int或unsigned int:... ——具有整数类型(除int或unsigned int之外)且其整数转换等级小于或等于int和unsigned int的对象或表达式。...如果int可以表示原始类型的所有值...,则将该值转换为int;否则,将其转换为unsigned int。这些被称为整数提升。... 3.整数提升保留值,包括符号。”

参见例如www.open-std.org/JTC1/SC22/WG14/www/docs/n1570.pdf

您可以使用以下代码,它可以正常工作:

      int i;
      for (i=3, ts=0; i>=0; --i) ts = (ts<<8) | a[i];

在示例代码中,所有被移位的 a[i] 都具有无符号的右侧,这是由于常量上的 U 前缀所致(例如 << 8U),这意味着根据这些规则,表达式 a[1] << 8U 应该已经具有无符号类型。 - user964970
罪魁祸首不是 a[0]。然而,将 a[0] 强制转换为 unsigned 会使按位或的结果成为 unsigned,这会截断出现在 a[3] << 24 中的符号扩展,这才是真正的罪魁祸首。 - Dietrich Epp

1
ts = ((unsigned long long)a[0]) | 
    ((unsigned long long)a[1] << 8U) | 
    ((unsigned long long)a[2] << 16U) | 
    ((unsigned long long)a[3] << 24U); 

强制类型转换可以防止将中间结果转换为默认的 int 类型。


1
但是,既然涉及的所有类型都是无符号类型,为什么会有一个中间int结果呢?罪魁祸首似乎只是第一个a[0],将其替换为(unsigned)a[0],一切就都好了。但是为什么呢? - user964970

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接