signed&&unsigned in the c language

来源:互联网 发布:免费下载矢量图 知乎 编辑:程序博客网 时间:2024/05/16 01:47
Gcc处理有符号字符和无符号字符分析 摘自 JDEV bloves 2004-01-16 18:46http://ccb.77jj.com/user03/bbs/ccb/index.cgiGcc是怎么处理有符号值和无符号值的?下面文章可能有错误,或理解不当的地方,希望你在仔细看过文章之后,如果发现错误,请指出错误所在,谢谢。 下面以2个不同而又类似的程序来分析gcc的处理过程,以及计算机中都做了些什么。首先来看program_one.c 和 program_two.c有什么不同,以及编译器是如何处理的:Program_one.c------------------------------- #include int main(int argc, char *argv[]) { char p=255; //ff printf ("%d",p); //这条语句就是把ASCII码的十进制值打印到屏幕上 //p是有符号的数 }program_two.c #include int main(int argc, char *argv[]) { unsigned char p=255; // 255为ASCII码,翻译成16进制就是0xff printf ("%d",p); }下面我们几来讨论一下:大家都知道在c中,字符分为有符号和无符号这两种形式。默认是为有符号既:char ch;这样的形式。通过unsigned char ch;这样的形式定义为无符号。那么到底这两种有什么不同的:编译program_one.c程序,然后用 gdb中的disass 命令反汇编main子程序:如下: #include int main(int argc, char *argv[]) { char p=255; //ff printf ("%d",p); //这条语句就是把ASCII码的十进制值打印到屏幕上 //255是有符号的数 } Dump of assembler code for function main: 0x401284
: push %ebp 0x401285 : mov %esp,%ebp 0x401287 : sub $0x8,%esp 0x40128a : and $0xfffffff0,%esp 0x40128d : mov $0x0,%eax 0x401292 : mov %eax,0xfffffff8(%ebp) 0x401295 : mov 0xfffffff8(%ebp),%eax 0x401298 : call 0x402920 <_alloca> 0x40129d : call 0x401360 <__main> 0x4012a2 : movb $0xff,0xffffffff(%ebp) // 复制一个字节到0xffffffff(%ebp)的堆栈区域 0x4012a6 : sub $0x8,%esp 0x4012a9 : movsbl 0xffffffff(%ebp),%eax // 字节0xff符号扩展为长字节到eax中,现在就是eax里存放的是ffffffff 0x4012ad : push %eax // 进栈 0x4012ae : push $0x401280 // 这个不清楚具体是什么意思,估计是转换个格式吧 0x4012b3 : call 0x4029c0 //ffffffff对应的10进制整数值就是-1.所以这里运行后会在屏幕上显示 -1 // 调用printf,把eax中的的按十进制打印到屏幕上 0x4012b8 : add $0x10,%esp movsbl 是字节符号扩展为长字节指令 下面按反汇编program_two的过程反汇编progaram_two.c程序,反汇编即分析结果如下:-------------------------------------------- 0x401284
: push %ebp 0x401285 : mov %esp,%ebp 0x401287 : sub $0x8,%esp 0x40128a : and $0xfffffff0,%esp 0x40128d : mov $0x0,%eax 0x401292 : mov %eax,0xfffffff8(%ebp) 0x401295 : mov 0xfffffff8(%ebp),%eax 0x401298 : call 0x402930 <_alloca> 0x40129d : call 0x401370 <__main> 0x4012a2 : movb $0xff,0xffffffff(%ebp) //这里的oxff就是ASCII码的值.把变量p中的值存放到0xffffffff(%ebp)的栈区里 0x4012a6 : sub $0x8,%esp 0x4012a9 : mov $0x0,%eax 0x4012ae : mov 0xffffffff(%ebp),%al 0x4012b1 : push %eax 0x4012b2 : push $0x401280 //参数的地址,这个原本我也不知道,gdb反汇编看不出来什么,所以我用gcc –S 来生成汇编代码看,结果接知道了,具体的请看: ------------------------------------------------------------------------------------------------------------------- .file "test.c" .def ___main; .scl 2; .type 32; .endef .text LC0: //这里就是我们反汇编$0x401280的地址符号 .ascii "%c/0" .align 2 .globl _main .def _main; .scl 2; .type 32; .endef _main: pushl %ebp movl %esp, %ebp subl $8, %esp andl $-16, %esp movl $0, %eax movl %eax, -8(%ebp) movl -8(%ebp), %eax call __alloca call ___main movb $-1, -1(%ebp) subl $8, %esp movl $0, %eax movb -1(%ebp), %al pushl %eax pushl $LC0 //果然是这样。他就是$0x401280的符号地址形式 //如果反汇编看,是看不出printf(“%d”.p);和printf(“%c”,p);的区别的,好在gcc强大。 //在这内部,printf函数,根据不同的参数进行不同的处理,如果是%d的话,那么只把p里的值以10进制的形式打印,--------------------------------------------------------------------------------------------------------------------0x4012b7 : call 0x4029d0 // 按整数的格式在屏幕上显示0xff,0xff对应的整数值就是255. 所以会打印2550x4012bc : add $0x10,%esp-----------------------------------------------------*/通过上面两个程序的分析,不难发现,编译器在处理有符号数和无符号数时不同的地方是,无符号时不做字节到长字节的符号扩展,而有符号数就需要做长字节扩展,这里的就是把0xff扩展为0xffffffff的形式,而这里printf(“%d”,p);我们的程序是打印255的整数形式,而并不是打印 ASCII码对应的值。如果打印对应的值,那么就是printf(“%c”,c);这样的形式。当如果我们写 #include int main(int argc, char *argv[]) { unsigned char p=255; printf ("%c",p); }这样的程序时,是%c的话,那需要经过一个查表过程,主要是查找ASCII码的对照表,比如0xff,那么就找0xff对应的字符值,oxff对应的是blank字符.就是空格。 [ bloves 2004-01-16 20:14 编辑 ] 摘自 JDEV bloves 2004-01-16 18:46http://ccb.77jj.com/user03/bbs/ccb/index.cgi 2004-01-16 18:46   编辑文章 引用回复 查看作者资料 给作者发悄悄话 查看作者的所有帖子 版主操作 删除文章 作者 回复: 编译器如何区别... 2 楼 blovesJDEV 社区区长积 分:123总数第:114 贴来 自:bloves 编译器如何区别... 摘自 JDEV bloves 2004-01-17 03:23http://ccb.77jj.com/user03/bbs/ccb/index.cgi编译器如何区别有符号还是无符号呢?一句话,根据词法unsigned char还是signed char或char来区分,如果是unsigned,那么其生成的代码中就不包括一条符号扩展指令,如果是signed 或直接char的话,那么将包含一个符号扩展指令.为什么会一个等于128另一个等于-128呢?因为第2个是有符号,那么就编译器就会生成符号扩展指令,因为无符号只能表示-128~127所以当127+1时就等于其另一段的开始,+2就等于-127,+3就等于-126。 #include int main(void){ unsigned char a=127; a=a+1; printf("a=%d/n",a); } 上面这个程序是无符号的,所以编译器不做符号扩展,所以输出128下面来看下面这个程序: int main(void){ char a=127; a=a+1; printf("a=%d/n",a); } 关键看这几条指令: call 0x401370 <__main> movb $0x7f,0xffffffff(%ebp) //0x7f 等于10进制127 lea 0xffffffff(%ebp),%eax incb (%eax) //127+1 等于128 也就是16进制80,2进制10000000,注意这是有符号数,并且符号位为1 sub $0x8,%esp movsbl 0xffffffff(%ebp),%eax //movsbl指令做符号位长字节扩展,也就是11111111111111111111111110000000 //因为符号为1,所以前面加上24个1 //11111111111111111111111110000000就是10进制-128 #include int main(void){ unsigned char a=122; a=a+1; printf("a=%d/n",a); }编译并运行程序,程序输出结果为: 123 #include int main(void){ char a=122; a=a+1; printf("a=%d/n",a); }编译并运行程序,程序输出结果为: 123 那为什么会出现这种结果呢?上面我们分析的不是有符号会得到符号的吗?这里其中的一个原因是有符号,只有超过了127,才会产生负的,也就是说127以前他的符号位都是0,127+1=128的符号位就为1了,所以会产生负的.下面我们来看看汇编代码: 0x4012a4 : movb $0x7a,0xffffffff(%ebp)//0x7a就是122,2进制01111010,注意这里的符号位为0 0x4012a8 : lea 0xffffffff(%ebp),%eax 0x4012ab : incb (%eax) //加1 0x4012ad : sub $0x8,%esp 0x4012b0 : movsbl 0xffffffff(%ebp),%eax //这里又产生了符号扩展指令,那为什么没扩展成负数呢? //因为这里的符号位为0,所以其扩展后值就是122+1=123 所以这样不会输出负数,关于gcc编译器生成有符号字符和无符号字符的本质就是有无扩展指令,扩展的符号位为0还是为1 Machine:Intel 80x86系 complier:Gcc 3.22 OS:windows2k
原创粉丝点击