将无符号整型转换为有符号整型C

mbyulnm0  于 2022-12-03  发布在  其他
关注(0)|答案(9)|浏览(318)

我正在尝试将65529unsigned int转换为带符号的int。我尝试进行如下类型转换:

unsigned int x = 65529;
int y = (int) x;

但是y在应该返回-7的时候仍然返回65529,这是为什么呢?

zbwhf8kr

zbwhf8kr1#

看起来您希望intunsigned int是16位整数,但显然不是这样,最有可能的是,它是32位整数--足够大,可以避免您所期望的回绕。
请注意,没有完全符合C语言的方法来实现这一点,因为超出范围的值的有符号/无符号转换是由实现定义的。但在大多数情况下,这仍然有效:

unsigned int x = 65529;
int y = (short) x;      //  If short is a 16-bit integer.

或者可替换地:

unsigned int x = 65529;
int y = (int16_t) x;    //  This is defined in <stdint.h>
yfjy0ee7

yfjy0ee72#

我知道这是个老问题,但这是个好问题,那么这个怎么样?

unsigned short int x = 65529U;
short int y = *(short int*)&x;

printf("%d\n", y);

这样做是因为我们将x的地址转换为它的类型的带符号版本,这是C标准允许的。不是所有像这样的类型双关语(事实上大多数)都是法律的的。标准是这样说的。
对象的储存值应该只能由具有下列其中一种型别的左值存取:

  • 对象的声明类型,
  • 对象的声明类型的限定版本,
  • 对应于对象的声明类型的有符号或无符号类型的类型,
  • 作为与对象的所声明类型的限定版本相对应的有符号或无符号类型的类型,
  • 在其成员中包括上述类型之一的集合或联合类型(递归地包括子集合或包含联合的成员),
  • 字符类型。

所以,唉,由于我们访问x的位时,就好像它们是带符号的(通过指针),实际的转换操作被替换为阅读看起来只是一个带负数号的短整型,并且转换顺利进行。然而,这在一个补码机器上可能会出错,但这些非常非常罕见,非常过时。我都懒得去照顾他们。

wgmfuz8q

wgmfuz8q3#

@Mysticial知道了,一个短的通常是16位的,会说明答案:

int main()  
{
    unsigned int x = 65529;
    int y = (int) x;
    printf("%d\n", y);

    unsigned short z = 65529;
    short zz = (short)z;
    printf("%d\n", zz);
}

65529
-7
Press any key to continue . . .

更详细一点。这是关于带符号的数字是如何存储在内存中的。搜索二进制补码表示法以了解更多详细信息,但这里是基础知识。
我们来看一下十进制的65529,它可以用十六进制表示为FFF9h,也可以用二进制表示为:
11111111 11111001
当我们声明short zz = 65529;时,编译器将65529解释为一个有符号值。在二进制补码表示法中,最高位表示一个有符号值是正还是负。在本例中,你可以看到最高位是一个1,因此它被视为一个负数。这就是为什么它输出-7
对于一个unsigned short,我们不关心符号,因为它是unsigned,所以当我们用%d打印它时,我们使用了所有的16位,所以它被解释为65529

ve7v8dk2

ve7v8dk24#

要理解其中的原因,您需要知道CPU使用2的补码表示有符号数(可能不是全部,而是许多)。

byte n = 1; //0000 0001 =  1
    n = ~n + 1; //1111 1110 + 0000 0001 = 1111 1111 = -1

另外,int和unsigned int类型的大小可能会因CPU的不同而不同。在执行以下特定操作时:

#include <stdint.h>
   int8_t ibyte;
   uint8_t ubyte;
   int16_t iword;
   //......
6l7fqoea

6l7fqoea5#

值65529 u和-7在16位整型中的表示是相同的,只是位的解释不同。
对于更大的int和这些值,需要sign extend;一种方法是使用逻辑运算

int y = (int )(x | 0xffff0000u); // assumes 16 to 32 extension, x is > 32767

如果速度不是问题,或者除法在处理器上很快,

int y = ((int ) (x * 65536u)) / 65536;

乘法左移16位(同样,假设16到32扩展),除法右移,保持符号不变。

r6vfmomb

r6vfmomb6#

你期望你的int类型是16位宽,在这种情况下你得到的实际上是一个负值。但它最有可能是32位宽,所以一个有符号的int可以很好地表示65529。你可以通过打印sizeof(int)来检查这一点。

l7mqbcuq

l7mqbcuq7#

要回答上面评论中的问题,请尝试以下内容:

unsigned short int x = 65529U;
short int y = (short int)x;

printf("%d\n", y);

unsigned short int x = 65529U;
short int y = 0;

memcpy(&y, &x, sizeof(short int);
printf("%d\n", y);
lmvvr0a8

lmvvr0a88#

由于转换无符号值用来表示正数,因此可以通过将最高有效位设置为0来完成转换。因此,程序不会将其解释为二进制补码值。一个警告是,这将丢失接近无符号类型max的数字的信息。

template <typename TUnsigned, typename TSinged>
TSinged UnsignedToSigned(TUnsigned val)
{
    return val & ~(1 << ((sizeof(TUnsigned) * 8) - 1));
}
mcvgt66p

mcvgt66p9#

我知道这是一个老问题,但我认为响应者可能误解了它。我认为它的目的是将接收到的16位位序列转换为无符号整数(技术上讲,是unsigned short)转换为有符号整数。当你需要将从网络接收到的数据从网络字节顺序转换为主机字节顺序时,可以使用union:

unsigned short value_from_network;
unsigned short host_val = ntohs(value_from_network);
// Now suppose host_val is 65529.
union SignedUnsigned {
  short          s_int;
  unsigned short us_int;
};
SignedUnsigned su;
su.us_int = host_val;
short minus_seven = su.s_int;

现在minus_seven的值为-7。

相关问题