弱类型?C语言参数提升带来的一个陷阱
很久以前,我接触的最初几本C语言书中,我记得有类似这么一句话“C语言是一种弱类型的语言,类型之间可以进行隐式的转换;而C++是强类型的语言,需要进行强制类型转换”。我忘了是哪本书,但这句话我一直记得。因为实际写代码中一直也没有触碰隐式的转换(我一般都会强制转换),所以也没有深究过这个问题。然而最近的一段代码却给我带来了一些困惑。
copyright@ www.spongeliu.com
先抛开我遇到的问题不说,简单回顾下C语言的隐式类型转换,如下一段代码:
#include <stdio.h> double sum(double x, double y); //声明一个函数 int main() { int x=10; int y=10; double z=sum(x,y); printf("%lf\n",z); return 0 } |
没问题,我们得到的结果是20.000000。
同时,上面代码中,首先声明了sum函数的原型“double sum(double x,double y);”。 熟悉C的人都知道,在“old style c”中,我们可以声明一个不带参数原型的函数,也就是这么声明“double sum();” 这表示sum函数可能有无限个参数,具体由函数定义来决定。那么,如果在上述代码中使用这种方式会怎么样呢?
double sum(); //声明一个函数 int main() { int x=10; int y=10; double z=sum(x,y); printf("%lf\n",z); return 0 } |
由于各个编译器的具体实现不同,我的实验环境是Linux+gcc4.4.5。在这样的编译环境下,你会发现程序得到了一个不确定的值!
很显然,这段代码跳入了一个陷阱。即时你在编译的时候加入了-Wall选项,也不会出现任何的警告!C既然允许我们声明一个参数未定义的函数原型,却为什么不让我们得到一个正确的程序呢?
让我们来看C99标准中section 6.5.2.2 "Function calls" 的Paragraphs 6, 7:
Paragraphs6
1、默认参数提升:如果一个函数的形参类型未知,那么调用函数时要对相应的实参做“整数提升(integer promotions)”,除此以外,float类型的参数会被提升为double。
2、如果形参和实参个数不相等的时候,行为未定义;
3、 如果函数定义的时候指定参数原型,那么a)参数原型包含(...),即变参;b)形参类型和实参类型不符合。这两种行为都是未定义的;
4、 如果函数定义的时候不指定参数原型,如果提升后的实参类型和形参类型不相符,则行为未定义;除了两种情况a)提升后一个是unsigned int一个是signed int,则值可以被表示成这两种的任何形式;b)实参或形参都是指针,分别指向限定和非限定(如const)的char或者void。
Paragraphs7
1、 如果一个函数的形参类型已知,则实参的类型会被隐式的转换成形参的类型,并且转换成非限定的对应类型;
2、如果函数原型中有(...)参数,那么对应的实参会被进行默认参数提升;
(以上纯属个人翻译,如想看原文,请参照c99标准手册)
你也许会稍许有些疑惑,什么情况下函数定义会没有原型(参数类型未知)呢?让我们来看一下:
首先,无论函数声明还是函数定义,都是可以没有原型的。如这样的代码:
1 2 |
void func(int a, char b, float c); void func(int a, char b, float c) { /* ... */ } |
line1是带参数原型的函数声明;line2是带参数原型的函数定义。
再看这样的代码:
1 2 3 4 5 6 |
void func(); void func(a, b, c) int a; char b; float c; { /* ... */ } |
line1是不带参数原型的函数声明;line2往后是不带参数原型的函数定义 (K&R C style)。
简单来说,以上标准可以归结为两点:
1、对于有参数原型的函数(非变参),实参会被隐式转换成相应的实参的类型;
2、对于没有参数原型的函数或者变参函数,实参会被进行“默认参数提升”
那现在来看,我们之前的问题出再哪里了?首先,sum函数声明的时候没有定义参数原型,因此main函数在调用sum的时候,对参数作了int类型的提升,而不是隐式的转换;其次,sum函数在定义的时候指定了参数的类型,其类型同提升后得到的int类型不相同,命中标准中Paragraphs6的第三条,得到一个未定义的行为,至于怎么处理,那就是编译器的事情了。
C语言的类型转换,除了上述的这些标准,还涉及到很多很复杂的事情,比如有符号、无符号、浮点等,每一种类型转换都要定义一种转换规则,而且不同的编译器不同的体系结构往往会带来不同的结果,很多类型转换都是C标准中未定义的,很可能就导致错误的出现。比如上述的这个例子,很显然是一个很阴暗的用法,我们在实际写程序的时候往往不会这么用,但是很不巧,我的一个程序中的一个函数参数很多,我在声明该函数的时候就偷懒没有指定参数的原型,而恰巧其中一个参数被作了提升而类型同函数定义的不一样,导致程序出错。因此,在C语言中,我们要尽量的避免使用隐式的类型转换。
此外,对于文章开头提到的“C语言是弱类型”,实在不敢苟同。其实争论一个语言是强弱类型本身就没有意义,但我个人认为,C语言这种在编译时就已经确定了数据类型的语言,如果硬要划分的话,怎么也不应该算是一个弱类型!
参考数目:
C和C++经典著作:C陷阱与缺陷
你必须知道的495个C语言问题
建议继续学习:
- STRUTS2类型转换错误导致OGNL表达式注入漏洞分析 (阅读:9255)
- PHP数据类型隐性转换的陷阱 (阅读:3010)
- javascript运算/转换技巧 (阅读:2730)
- JavaScript ( (__ = !$ + $)[+$] + ({} + $)[_/_] +({} + $)[_/_] ) (阅读:2610)
- 编写安全代码:再论整数类型转换 (阅读:2680)
- PHP类型转换相关的一个Bug (阅读:1984)
- 当类型转换表达式遇上自定义转换操作 (阅读:1525)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:sponge 来源: SpongeLiu的blog
- 标签: 弱类型 类型转换
- 发布时间:2012-09-19 00:00:59
- [70] IOS安全–浅谈关于IOS加固的几种方法
- [69] Twitter/微博客的学习摘要
- [64] 如何拿下简短的域名
- [63] android 开发入门
- [62] Go Reflect 性能
- [61] find命令的一点注意事项
- [59] 流程管理与用户研究
- [58] 图书馆的世界纪录
- [57] Oracle MTS模式下 进程地址与会话信
- [56] 【社会化设计】自我(self)部分――欢迎区