C语言unsigned与signed使用辨析

本文探讨了C++中signed与unsigned修饰符的实际作用,包括它们如何影响整型变量的存储、类型转换及表达式运算。并通过实例展示了在printf函数中使用不同的格式控制符输出signed与unsigned类型变量的结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一些说明

  • 操作系统: windows xp x32
  • 编译器:VC++ 6.0
  • 测试只针对整型变量,因为浮点型变量的IEEE存储方式本身就包含了符号位。

使用场景

  • 定义一个整型变量x;,如果我们会在x中存储有符号数,则定义(signed) int x;(signed可以省略),如果只会存储无符号数则定义为unsigned int x;
    很多书上都是这么写:

有符号int取值范围:-2,147,483,648~2,147,483,647‬
无符号int取值范围:0~4,294,967,295

Q1:unsigned修饰的类型只能存储无符号数,signed修饰的类型只能存储有符号数?

  • 口说无凭,还是以int类型为例,分别存储超过取值范围的数据到变量中
int x = 4294967295;
unsigned int y = -2147483648;

经VC 6.0编译通过,表明修饰符signedunsigned并没有在编译器层面进行限制。

  • 理解:unsigned修饰符并不影响int类型的宽度(4个字节),用4个字节肯定可以表示有符号数或无符号数取值范围内的任意一个数,所以这样存储是没有问题的。

那么如果printf()函数打印结果会怎么样?
在这里插入图片描述

图1 %d格式控制符输出

好的,问题又来了,有符号数打印结果是-1似乎可以让人信服,为什么无符号数打印结果是负数?先换个思路,可能是格式控制符的问题,这里我们用的是%d,那么如果换成专门打印无符号数的%u会怎么样?
在这里插入图片描述

图2 %u格式控制符输出

此时变量y的打印结果似乎正常了,但是变量x的打印结果又开始扑朔迷离。根据实践得出的结论是:当使用函数printf()打印时,格式控制符告诉编译器按什么数输出,%d表示按十进制有符号数输出,%u表示按十进制无符号数输出
有了以上结论,输出的怪异现象就很好解释了。在使用printf()函数打印输出时,使用%d还是%u是由程序员决定的,也就是说程序员决定了该数按什么规则处理输出。
两张截图输出结果解释:

  1. 图1中按十进制有符号数输出x和y,x=4294967295转换为16进制就是0xFFFFFFFF,对应有符号数-1,所以x输出-1;y = -2147483648本身就是有符号数中的最小的负数,所以直接输出。
  2. 图2中按十进制无符号数输出x和y,x=4294967295是无符号数中最大的数,直接输出;y = -2147483648转换为正数就是2147483648(这里涉及到补码相关知识)。

Q2:基于以上结论,编译器并不对signed、unsigned修饰的变量取值进行限定,那么它们有什么用途?

用途1 类型转换

在这之前先看两条汇编指令:MOVSX、MOVZX

	MOV AL,0FF ; AL=0FF
	MOVSX ECX,AL ; MOVSX中的S表示Sign
  • 上述指令的意思是先将0FF赋值给寄存器AL,MOVSX ECX,AL就是将AL视作有符号数扩展到ECX,AL的最高位即为符号位,由于AL最高位为1,所以扩展后的前24位全为1,ECX=0xFFFFFFFF
	MOV AL,0FF ; AL=0FF
	MOVZX ECX,AL ; MOVZX中的Z表示Zero
  • 上述指令的意思是先将0FF赋值给寄存器AL,MOVZX ECX,AL就是将AL视做无符号数扩展到ECX中,所以扩展过程直接在扩展后的前24位添0,ECX=0x000000FF

看一个类型转换的例子

char x = 0xFF;
int y = x;
unsigned char z = 0xFF;
int k = z;

查看上述代码的反汇编得到以下结果
在这里插入图片描述

  1. x转化到y进行了符号拓展,所以y=[ebp-8]=0xFFFFFFFF
  2. z转化到k进行了类似于MOVZX的转换,所以z=[ebp-10h]=0x000000FF

小结1

  • 进行类型转换时,只与原类型本身有关,如果原类型加了unsigned修饰符则为MOVZX扩展,如果没有加unsigned则为MOVSX扩展。加不加unsigned决定了扩展时填充的是0还是1。
用途2 表达式运算
  • 在表达式中出现不同类型的变量时,运算过程会转换成4字节宽度进行运算(针对整型变量)
char x = 12;
short y = 13;
printf("%d\n",x+y);
if (x+y>20)
{
	printf(">20\n");
}

查看反汇编:
在这里插入图片描述
小结2

  1. 含有不同类型的表达式在运算时会扩展到4字节进行运算,在扩展时其实就遵循了小结1里的规则。
  2. printf()在打印整型变量时,会先扩展成4字节。
    读者可以尝试如下代码:
char x = -1;
printf("%u",x);
提示:如果x不扩展,打印结果应该为255

结论

  1. 修饰符unsignedsigned并不在编译器层面限制整数类型中存储的数值,signed修饰的类型也可以存储超过有符号数范围的正数,unsigned修饰的类型也可以存储负数。
  2. printf()函数输出时,程序员使用格式控制符%d%u告诉了函数按有符号数还是无符号数打印数值。
  3. 类型转换(小转大)时,根据小类型的修饰符来进行扩展。
  4. 表达式运算时,小类型会根据修饰符先进行零扩展或有符号扩展。
  5. 为避免这些奇奇怪怪的问题,建议用unsigned修饰的整数类型用%u输出,没有用unsigned修饰的类型用%d输出。
### C语言 `unsigned` 类型使用说明 #### 定义基本概念 在C语言中,`unsigned` 表示无符号类型的数据。这意味着该类型的变量仅能存储非负数值,即零或正数。相对应地,带有 `signed` 关键字的类型可以表示负数,默认情况下大多数整数类型被假定为带符号的[^1]。 #### 数据范围对比 对于相同长度的不同符号属性的整数类型来说,它们所能表达的最大值会有所不同。例如: - 对于一个8位的 `char` 类型而言, - 如果它是 `signed char`,则其取值区间是从−128到+127; - 若定义成 `unsigned char` 则变为从0至255[^3]。 #### 常见应用实例 当处理那些本质上不可能出现负数的情景时(比如数组索引),推荐采用 `unsigned` 整形以提高程序逻辑的一致性可读性。此外,在某些特定场景下利用 `unsigned` 可以更高效地执行按位运算操作,因为不需要考虑符号扩展等问题。 #### 函数参数传递中的注意事项 函数声明时如果涉及到指针作为输入参数,则需要注意所指向的对象是否应该指定为 `unsigned` 类型。例如给定的一个设备控制接口可能接受命令码作为一个 `unsigned int` 参数以及额外数据通过另一个 `unsigned long` 形参传入[^2]: ```c int ioctl(struct inode*, struct file*, unsigned int cmd, unsigned long arg); ``` #### 打印输出格式化字符串的选择 为了正确显示 `unsigned` 类型的结果,在调用标准库函数如 `printf()` 进行格式化输出的时候要选用合适的转换规格符。具体来讲,针对不同宽度的无符号整形应当分别运用 `%u`, `%lu`, 或者其他相应形式[^4]: | 类型 | 转换字符 | | --- | --- | | `unsigned int` | `%u` | | `unsigned long` | `%lu` | 下面给出一段简单的代码片段用于展示如何打印一个 `unsigned int` `unsigned long` 的值: ```c #include <stdio.h> void print_unsigned_values(void){ unsigned int ui = 42; unsigned long ul = 123456L; printf("Unsigned Int Value: %u\n", ui); // 正确的方式 printf("Unsigned Long Value: %lu\n", ul); // 正确的方式 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值