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

前不久和同事谈论起 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 
#include 

int main()
{
    int arr[]={1,2,3,4};
    if(-1

按照我们平时的逻辑: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

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

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

#include 
#include 

int main()
{
    int arr[]={1,2,3,4};
    if((double)-1
注意,这次我们在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
注意第22,23,24行,test  eax, eax  与运算的结果为1,je不跳转,就继续执行了hello world。

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

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

在例一中,if(-1

在例二中,我们将-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)

你可能感兴趣的:(C)