unsigned 关键字,你真的懂了吗?

本文通过两个示例探讨了unsigned类型在与其他类型进行混合运算时的行为,并解释了编译器如何根据ANSIC标准进行类型转换。

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

前不久和同事谈论起 unsigned 关键字,故今天小结一下。

以32位机为例,int 分为无符号 unsigned 和有符号 signed 两种类型,默认为signed。二者的区别就是无符号类型能保存2倍于有符号类型的数据。32位下,signed int 的表示范围为:-2147483648 ~ 2147483647 (最高位做符号位)。unsigned int 的表示范围为:0 ~ 4294967295 (不保留符号位)。我们都知道,两个不同的数据类型在进行混合使用时,会自动进行类型转换。其转换原则就是:向着精度更高、长度更长的方向转换。也就是我们平常见到的 char 转为 int ,int 转为 long,float 转为 double  . etc. 那么当涉及到unsigned 类型时,又会进行怎样转换呢?下面来看个小例子:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int arr[]={1,2,3,4};
    if(-1<sizeof(arr)/sizeof(arr[0]))
        printf("Hello world!\n");
    else
        printf("Error!\n");
    return 0;
}

按照我们平时的逻辑:sizeof(arr)=4 (数组长度), sizeof(arr[0]) =4 (int 所占字节),所以,if(-1<1) 成立,执行hello world。可事实却相反,输出为Error。

下面我们看一下它的反汇编:(不想看的童鞋可以跳过)

int main()
{
00F31690  push        ebp  
00F31691  mov         ebp,esp  
00F31693  sub         esp,0DCh  
00F31699  push        ebx  
00F3169A  push        esi  
00F3169B  push        edi  
00F3169C  lea         edi,[ebp-0DCh]  
00F316A2  mov         ecx,37h  
00F316A7  mov         eax,0CCCCCCCCh  
00F316AC  rep stos    dword ptr es:[edi]  
00F316AE  mov         eax,dword ptr [___security_cookie (0F37000h)]  
00F316B3  xor         eax,ebp  
00F316B5  mov         dword ptr [ebp-4],eax  
    int arr[]={1,2,3,4};
00F316B8  mov         dword ptr [ebp-18h],1  
00F316BF  mov         dword ptr [ebp-14h],2  
00F316C6  mov         dword ptr [ebp-10h],3  
00F316CD  mov         dword ptr [ebp-0Ch],4  
    if(-1<sizeof(arr)/sizeof(arr[0]))
00F316D4  xor         eax,eax  
00F316D6  je          main+61h (0F316F1h)  
        printf("Hello world!\n");
00F316D8  mov         esi,esp  
00F316DA  push        offset string "Hello world!\n" (0F3577Ch)  
00F316DF  call        dword ptr [__imp__printf (0F382CCh)]  
00F316E5  add         esp,4  
00F316E8  cmp         esi,esp  
00F316EA  call        @ILT+320(__RTC_CheckEsp) (0F31145h)  
    else
00F316EF  jmp         main+78h (0F31708h)  
        printf("error\n");
00F316F1  mov         esi,esp  
00F316F3  push        offset string "error\n" (0F35774h)  
00F316F8  call        dword ptr [__imp__printf (0F382CCh)]  
00F316FE  add         esp,4  
00F31701  cmp         esi,esp  
00F31703  call        @ILT+320(__RTC_CheckEsp) (0F31145h)  
    return 0;
00F31708  xor         eax,eax  
}

注意一下第22,23行,xor     eax,eax  异或运算的结果为0,所以总会跳转至0F316F1h。执行Error语句。

下面我们再修改一下这个程序:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int arr[]={1,2,3,4};
    if((double)-1<sizeof(arr)/sizeof(arr[0]))
        printf("Hello world!\n");
    else
        printf("error\n");
    return 0;
}
注意,这次我们在if语句里,将-1转换成了double类型。编译运行一下:hello world 。我们想要的结果终于出来了,在我们欣喜的同时,不禁要问:why ? 

我们再来看一下它的汇编:

int main()
{
00BE1690  push        ebp  
00BE1691  mov         ebp,esp  
00BE1693  sub         esp,0DCh  
00BE1699  push        ebx  
00BE169A  push        esi  
00BE169B  push        edi  
00BE169C  lea         edi,[ebp-0DCh]  
00BE16A2  mov         ecx,37h  
00BE16A7  mov         eax,0CCCCCCCCh  
00BE16AC  rep stos    dword ptr es:[edi]  
00BE16AE  mov         eax,dword ptr [___security_cookie (0BE7000h)]  
00BE16B3  xor         eax,ebp  
00BE16B5  mov         dword ptr [ebp-4],eax  
    int arr[]={1,2,3,4};
00BE16B8  mov         dword ptr [ebp-18h],1  
00BE16BF  mov         dword ptr [ebp-14h],2  
00BE16C6  mov         dword ptr [ebp-10h],3  
00BE16CD  mov         dword ptr [ebp-0Ch],4  
    if((double)-1<sizeof(arr)/sizeof(arr[0]))
00BE16D4  mov         eax,1  
00BE16D9  test        eax,eax  
00BE16DB  je          main+66h (0BE16F6h)  
        printf("Hello world!\n");
00BE16DD  mov         esi,esp  
00BE16DF  push        offset string "Hello world!\n" (0BE577Ch)  
00BE16E4  call        dword ptr [__imp__printf (0BE82CCh)]  
00BE16EA  add         esp,4  
00BE16ED  cmp         esi,esp  
00BE16EF  call        @ILT+320(__RTC_CheckEsp) (0BE1145h)  
    else
00BE16F4  jmp         main+7Dh (0BE170Dh)  
        printf("error\n");
00BE16F6  mov         esi,esp  
00BE16F8  push        offset string "error\n" (0BE5774h)  
00BE16FD  call        dword ptr [__imp__printf (0BE82CCh)]  
00BE1703  add         esp,4  
00BE1706  cmp         esi,esp  
00BE1708  call        @ILT+320(__RTC_CheckEsp) (0BE1145h)  
    return 0;
00BE170D  xor         eax,eax  
}
注意第22,23,24行,test  eax, eax  与运算的结果为1,je不跳转,就继续执行了hello world。

如此看来,是编译器在编译源代码的时候,做了一些我们不知道的工作。查一下文档,找到这么一段:

ANSI C 标准采用值保留(value preserving)原则,就是当把几个整型操作数混合使用时,其结果的类型可能是有符号数,也可能是无符号数,这取决于操作数的类型的相对大小。(通俗点说,就是两个整型数,如果都转换为signed不会丢失信息,就转换为signed;否则就转换为unsigned。)

在例一中,if(-1<sizeof(arr)/sizeof(arr[0])) 由于signed int 和unsigned int 都转换为 signed 时,unsigned 会丢失信息(unsigned正数表示范围是signed的2倍,当然会丢失信息了)。故根据ANSI C 标准,两者都转换为 unsigned 类型。而-1 转为无符号类型为:4294967296 。是的,你没看错,-1 在计算机内以补码形式存放,转换成十进制后,就变成了这个巨大的正数:4294967296。所以,if(4294967296<1) 永远为假,一直输出Error了。

在例二中,我们将-1先转换成了8个字节的double类型。然后再和4个字节的unsigned类型比较时,由于double完全容得下unsigned。所以,此时,操作数都转换为有符号的double类型。所以 if( (double)-1 < (double)1) 成立。此时,就输出 hello world了。

总结:尽量不要在你的代码中使用无符号类型,以免增加不必要的复杂性;或者使用时,在涉及混合运算时,进行强制类型转换,这样就不必由编译器来选择转换类型了。

------------------------------------------------------------------------------------------------------

补充一点:

1,ANSI C 标准中采用的是值保留原则。而K&R C 标准中,采用的是无符号保留(unsigned preserving)原则,即:不区分操作数大小,统一转换为无符号类型。如果采用K&R C 标准的话,那么例一、例二就都会输出Error了。

2,当前编译器基本都遵循ANSI C 标准。如:gcc默认GNU C。

即:GNU C = C90 + GNU Extensions = C90 + (some features in C99 + some features in C11)

<think>嗯,用户问的是为什么在C语言中,unsigned关键字不能用于浮点数类型。这个问题看起来涉及到C语言的数据类型设计,我得仔细想想怎么回答比较好。 首先,我记得在C语言里,unsigned通常用来修饰整型,比如unsigned int或者unsigned char,这样变量只能表示非负数,范围也更大一些。但浮点数类型,比如float、double,确实不能用unsigned修饰。那为什么呢? 可能的原因有几个。首先,浮点数的标准,比如IEEE 754,已经规定了它们的表示方式,包括符号位、指数和尾数。如果去掉符号位,也就是用unsigned的话,浮点数的结构会被改变。而现有的硬件大多支持有符号的浮点数,所以如果C语言允许unsigned浮点,可能需要额外的处理,兼容性可能有问题。 然后,应用场景的问题。unsigned整型常用于位运算或者需要更大正数范围的场合,但浮点数通常用来表示有正负的实数,比如温度、坐标等。虽然理论上可能有只需要正浮点数的情况,但这种情况相对较少,可能不值得为此修改语言标准。 还有一个点是,浮点数的表示方式使得去掉符号位后,数值范围的变化不像整型那样直观。整型的unsigned直接扩展了最大值,但浮点数因为有指数部分,去掉符号位可能并不会简单地让范围翻倍,反而可能影响精度或者导致其他复杂问题。 另外,C语言的设计哲学是保持简洁和高效,不添加不必要的特性。如果增加unsigned浮点类型,可能需要调整很多底层实现,而收益不大,所以没有被纳入标准。 不过,我需要验证这些想法是否正确。比如,是否浮点数的符号位确实是单独的一部分,而如果去掉的话需要重新设计整个结构?是的,IEEE 754标准中的浮点数确实有一个符号位,然后是指数和尾数。如果unsigned的话,符号位可能被用来扩展尾数或指数,但这会改变整个编码方式,导致和现有硬件不兼容。 另外,有没有可能用户的实际需求是希望扩展浮点数的正数范围?这时候可能需要用更大的浮点类型,比如double变long double,而不是用unsigned。或者,用户可能对浮点数的存储结构不太清楚,所以需要解释为什么不能简单地像整型那样用unsigned来扩展范围。 总结下来,主要原因应该是浮点数的存储结构已经固定,使用unsigned会破坏兼容性,而且实际需求不大,语言设计者认为没有必要支持。同时,浮点数的数值表示方式和整型不同,不能简单地通过去掉符号位来扩展范围,反而会引起更多问题。</think>在C语言中,`unsigned`关键字不能用于浮点数类型(如`float`、`double`),这主要与以下设计原因和底层实现逻辑相关: --- ### 1. **浮点数的标准化表示** - 浮点数的格式(如IEEE 754标准)已经固定了存储结构:符号位、指数部分和尾数(有效数字)共同组成一个浮点数。 - **符号位**是浮点数的核心组成部分,用于表示正负。若强行移除符号位(类似`unsigned`对整型的作用),会破坏浮点数的标准编码方式,导致硬件和软件无法直接兼容现有规范。 --- ### 2. **应用场景的缺失** - `unsigned`对整型的意义是扩大非负数的表示范围(例如`unsigned int`的范围是`0 ~ 2^32-1`,而有符号的`int`是`-2^31 ~ 2^31-1`)。 - **浮点数的核心用途**是表示实数(包含正负和小数),而非仅非负数。即使需要仅表示正数,程序员可以直接忽略负值(例如通过`fabs()`取绝对值),无需依赖类型系统的强制约束。 - 若强行定义`unsigned float`,其“扩展范围”的效果并不明显,因为浮点数的范围由指数部分决定,符号位仅占1位,移除后的收益远小于整型。 --- ### 3. **硬件支持的复杂性** - 现代CPU的浮点运算单元(FPU)直接支持标准浮点格式(如IEEE 754),但硬件层面并未定义“无符号浮点数”的运算规则。 - 若引入`unsigned float`,需额外设计新的硬件指令和运算逻辑(例如如何处理溢出、舍入规则等),这与C语言追求高效和硬件直接映射的设计目标相悖。 --- ### 4. **语言设计的简洁性** - C语言的设计哲学是“保持简单”,避免冗余特性。由于无符号浮点数的实际需求极少,且可通过现有机制(如忽略负值)间接实现,因此未被纳入标准。 --- ### 替代方案 如果需要在代码中强制使用非负浮点数,可以通过以下方式实现: ```c float x = ...; if (x < 0.0f) { // 处理非法负值或强制取绝对值 x = -x; } ``` --- ### 总结 `unsigned`关键字不适用于浮点数,是因为浮点数的存储结构、硬件支持和实际需求均与整型不同。C语言选择遵循标准化和简洁性,而非添加一个收益有限且实现复杂的特性。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值