IT技术博客大学习 共学习 共进步

类型转换-无处不在的陷阱

Programming Life with Music 2009-11-17 23:15:54 浏览 3,202 次

今天在论坛上看有laphon同学的一个问题,觉得这个问题很有意思。
原帖地址
问题援引如下:

做一个小程序的时候发现的。代码如下,使用的编译器为DEV-C++ 4.9.9.2。
如果先要求输入a,再要求输入b,那么a的值无论输入多少(少于255)输出都会是0;
反过来,如果先要求输入b,再要求输入a,那么就会正常。
这是为什么呢?请教达人解释。


devcpp用的是gcc编译器,自己用gcc试了一下,果然是这样。为什么出现这种问题呢?开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应该是针对c标准库函数设计的)。

这种转型实际上C++其实是不允许的。比如

编译器(g++)会毫不客气的给出一个error:

但是实际上那段“问题代码”仍然可以编译通过。为什么呢?查看了stdio.h,scanf函数的原型如下:

printf最后一个参数”…”是一个变长参数,传参时实际传了一个void*进去,真正的类型分析和转型动在printf函数定义内的va_arg宏完成。所以编译器在分析这个函数调用时是无法知到真正的参数类型的。而scanf定义部分早被编译成了2进制lib。所以这段代码在c++中也可以编译通过。

另外有人说Intel C编译器没有这个问题,自己试了一下vc也没这个问题,这令我很费解。个人觉得这个问题似乎很难避免,因为这种用变长参数列表传递的参数,参数类型完全由前面的format string决定,可惜搞不到源码,就没法深究了。

建议继续学习

  1. PHP上传文件类型彻底判断方案及PHP+nginx上传大小彻底控制方案 (阅读 4,942)
  2. linux file命令是如何识别文件的类型的 (阅读 4,943)
  3. PHP JAVA C上传文件如何准确判断文件类型-mime知识普及 (阅读 4,504)
  4. JavaScript性能陷阱 (阅读 4,042)
  5. Java陷阱(2010版) (阅读 4,001)
  6. PHP数据类型隐性转换的陷阱 (阅读 3,942)
  7. 移动互联网系统架构十大陷阱 (阅读 3,644)
  8. 检查 Linux 下线程库的类型 (阅读 3,602)
  9. JavaScript 中的陷阱 (阅读 3,383)
  10. Zend Parameters Parser新增类型描述符介绍 (阅读 3,342)