类型转换-无处不在的陷阱
今天在论坛上看有laphon同学的一个问题,觉得这个问题很有意思。
原帖地址
问题援引如下:
做一个小程序的时候发现的。代码如下,使用的编译器为DEV-C++ 4.9.9.2。
如果先要求输入a,再要求输入b,那么a的值无论输入多少(少于255)输出都会是0;
反过来,如果先要求输入b,再要求输入a,那么就会正常。
这是为什么呢?请教达人解释。#include #include int main(int argc,char **argv) { unsigned char a,b; scanf(\"%d\",&a); scanf(\"%d\",&b); printf(\"a=%d,b=%d\\n\",a,b); scanf(\"%d\",&b); scanf(\"%d\",&a); printf(\"a=%d,b=%d\\n\",a,b); system(\"pause\"); }
devcpp用的是gcc编译器,自己用gcc试了一下,果然是这样。为什么出现这种问题呢?开gdb调试,信息如下:
main (argc=1, argv=0xbfbaf3e4) at a.c:7 7 scanf(\"%d\",&a); (gdb) n 1 8 scanf(\"%d\",&b); (gdb) p a $1 = 1 \'\\001\' (gdb) n 2 9 printf(\"a=%d,b=%d\\n\",a,b); (gdb) p a $2 = 0 \'\\0\' (gdb) p b $3 = 2 \'\\002\' (gdb)
从第12行可以看出,在第一次scanf读入b后,a的值被清0了,这样原因就好分析了。用%d读b的时候,实际上把b转型成了int,即scanf(”%d”,&b)实际上等于 int * p = &b; *p = 2 ,因为scanf接受的参数是指针。问题明显了,int4个字节,char1个字节,scanf实际上向内存里写了4个字节,高地址的3个字节为0,把a的1个字节和前面的参数区的2个字节给覆盖了。
如果声明顺序反过来,b在高地址,就不会把a覆盖,覆盖掉的是栈头部的参数区的3个字节。
但是无论那种方法,都是危险的,都会造成数据丢失。gcc 在开启 -Wall 开关后会给出警告
warning: format ‘%d’ expects type ‘int *’, but argument 2 has type ‘char *’
(这个warning应该是针对c标准库函数设计的)。
这种转型实际上C++其实是不允许的。比如
char a; int * p = &a;
编译器(g++)会毫不客气的给出一个error:
error: cannot convert ‘char*’ to ‘int*’ in initialization
但是实际上那段“问题代码”仍然可以编译通过。为什么呢?查看了stdio.h,scanf函数的原型如下:
extern int scanf (__const char *__restrict __format, ...)
printf最后一个参数”…”是一个变长参数,传参时实际传了一个void*进去,真正的类型分析和转型动在printf函数定义内的va_arg宏完成。所以编译器在分析这个函数调用时是无法知到真正的参数类型的。而scanf定义部分早被编译成了2进制lib。所以这段代码在c++中也可以编译通过。
另外有人说Intel C编译器没有这个问题,自己试了一下vc也没这个问题,这令我很费解。个人觉得这个问题似乎很难避免,因为这种用变长参数列表传递的参数,参数类型完全由前面的format string决定,可惜搞不到源码,就没法深究了。
建议继续学习:
- linux file命令是如何识别文件的类型的 (阅读:4029)
- PHP上传文件类型彻底判断方案及PHP+nginx上传大小彻底控制方案 (阅读:3893)
- PHP JAVA C上传文件如何准确判断文件类型-mime知识普及 (阅读:3619)
- JavaScript性能陷阱 (阅读:3161)
- Java陷阱(2010版) (阅读:3312)
- PHP数据类型隐性转换的陷阱 (阅读:3010)
- 移动互联网系统架构十大陷阱 (阅读:2895)
- 检查 Linux 下线程库的类型 (阅读:2683)
- Zend Parameters Parser新增类型描述符介绍 (阅读:2412)
- JavaScript 类型浅解 (阅读:2434)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:Jackal 来源: Programming Life with Music
- 标签: 类型 陷阱
- 发布时间:2009-11-17 23:15:54
- [69] IOS安全–浅谈关于IOS加固的几种方法
- [68] Twitter/微博客的学习摘要
- [63] 如何拿下简短的域名
- [62] android 开发入门
- [61] Go Reflect 性能
- [60] find命令的一点注意事项
- [58] 流程管理与用户研究
- [57] 图书馆的世界纪录
- [55] Oracle MTS模式下 进程地址与会话信
- [55] 读书笔记-壹百度:百度十年千倍的29条法则