64位环境 0和NULL 的区别
时间:2010-10-15
来源:互联网
本帖最后由 yjh777 于 2010-10-15 23:10 编辑
0 & NULL
在C语言中将值为0的指针做为NULL, NULL通常被定义为0或((void *)0);
有很多应该使用NULL的地方 写0代替的程序,,通常这样的写法也不会发生问题,如:
char *p = 0;
if (p != 0) ...
此时p为char *, 进行运算前,会将操作数类型转换为与之有互换性的某个类型上,所以默认int型的 0 转换为 char * 型的NULL,
这样一来,下面的书写方法也没有问题:
if (p) ...
在此时if会用布尔值来判断,用表达式的布尔值是否为0来判断。如下所示:
if (p != 0)
相反的条件
if (!p)
也跟
if (p == 0)
等价
象这样在多数情况下,用0代替NULL的处理方式也可以。但是也存在不通用的地方。
0 和 NULL 不同的情况
如前所述,NULL并不是int型的0值,而是指针型的0值(值为0的指针); 也就是说编译器必须要明白是用指针类型来解释,,
在x86那样的 ILP32 环境中由于 int long 指针都是32位,所以 int 型的 0 可以等同于指针的 NULL。但在IA64那样的LP64环境
中int仍然是32位的,但long和指针都是64位的,int的0便不等同于NULL了。 使用可变长参数时,这个问题很明显。
示例代码:
struct s *foo(const char *name, ...)
{
va_list va;
struct s *sp;
char *p;
va_start(va, fmt);
sp = (struct s *)malloc(*sp);
if (!sp) {
return 0;
}
memset(sp, 0, sizeof(*sp));
set_name(sp, name);
while ( (p = va_arg(va, char *)) ) {
set_item(sp, p);
}
return sp;
}
...
sp = foo("foo", "bar", 0);
...
这里的foo()是取得可变长参数后,利用name这个名称和剩下的参数表初始化Item结构体的函数。这个代码在LP64环境下不能正常运行。
调用 foo("foo", "bar", 0) 之后,参数会象下面这样 堆积在栈上:
为0的int
指向 "bar" 的const char *
指向 "foo" 的const char *
在 foo() 运行时, 型参const char *name 指向 第一个参数 "foo",其他变参通过va_list来访问,在foo()中通过如下形式取va_list内容:
p = va_arg(va, char *);
也就是参数作为char *来取出,第一个是指向"bar"的指针,所以没问题。 而第二个变参传的是int型(32位)的0,但读取时是按64位的
char *来取的,多读取了4字节未初始化的内存数据 如果这四个字节不是全0,而导致进入下一次while循环,会发生什么后果呢,,,
这就是将0和NULL混用而导致的bug,正确调用foo()的方法:
sp = foo("foo", "bar", NULL);
也有自己编程使用类似函数的情况,例如:使用类似机制的execl(3)时,必须用NULL来终止参数列表(见man手册提示):
int execl(const char *path, const char *arg, ... /*, (char *)0 */);
0 & NULL
在C语言中将值为0的指针做为NULL, NULL通常被定义为0或((void *)0);
有很多应该使用NULL的地方 写0代替的程序,,通常这样的写法也不会发生问题,如:
char *p = 0;
if (p != 0) ...
此时p为char *, 进行运算前,会将操作数类型转换为与之有互换性的某个类型上,所以默认int型的 0 转换为 char * 型的NULL,
这样一来,下面的书写方法也没有问题:
if (p) ...
在此时if会用布尔值来判断,用表达式的布尔值是否为0来判断。如下所示:
if (p != 0)
相反的条件
if (!p)
也跟
if (p == 0)
等价
象这样在多数情况下,用0代替NULL的处理方式也可以。但是也存在不通用的地方。
0 和 NULL 不同的情况
如前所述,NULL并不是int型的0值,而是指针型的0值(值为0的指针); 也就是说编译器必须要明白是用指针类型来解释,,
在x86那样的 ILP32 环境中由于 int long 指针都是32位,所以 int 型的 0 可以等同于指针的 NULL。但在IA64那样的LP64环境
中int仍然是32位的,但long和指针都是64位的,int的0便不等同于NULL了。 使用可变长参数时,这个问题很明显。
示例代码:
struct s *foo(const char *name, ...)
{
va_list va;
struct s *sp;
char *p;
va_start(va, fmt);
sp = (struct s *)malloc(*sp);
if (!sp) {
return 0;
}
memset(sp, 0, sizeof(*sp));
set_name(sp, name);
while ( (p = va_arg(va, char *)) ) {
set_item(sp, p);
}
return sp;
}
...
sp = foo("foo", "bar", 0);
...
这里的foo()是取得可变长参数后,利用name这个名称和剩下的参数表初始化Item结构体的函数。这个代码在LP64环境下不能正常运行。
调用 foo("foo", "bar", 0) 之后,参数会象下面这样 堆积在栈上:
为0的int
指向 "bar" 的const char *
指向 "foo" 的const char *
在 foo() 运行时, 型参const char *name 指向 第一个参数 "foo",其他变参通过va_list来访问,在foo()中通过如下形式取va_list内容:
p = va_arg(va, char *);
也就是参数作为char *来取出,第一个是指向"bar"的指针,所以没问题。 而第二个变参传的是int型(32位)的0,但读取时是按64位的
char *来取的,多读取了4字节未初始化的内存数据 如果这四个字节不是全0,而导致进入下一次while循环,会发生什么后果呢,,,
这就是将0和NULL混用而导致的bug,正确调用foo()的方法:
sp = foo("foo", "bar", NULL);
也有自己编程使用类似函数的情况,例如:使用类似机制的execl(3)时,必须用NULL来终止参数列表(见man手册提示):
int execl(const char *path, const char *arg, ... /*, (char *)0 */);
作者: yjh777 发布时间: 2010-10-15
本帖最后由 krein8964 于 2010-10-16 00:32 编辑
字面量 0 ,是int型,在64位系统里是32位。
宏 NULL, 定义为((void*)0),在64位系统里是64位。
在foo函数定义之后使用该函数,或在使用foo函数之前声明一下foo函数,编译器就会自动将int型的0转为void*型的NULL。
如果没有定义或声明foo函数就直接使用foo函数,编译器会根据你传入的参数产生一个隐含的声明。
调用foo("foo", "bar", 0),编译器会认为你的函数声明为int foo(char*, char*, int),并且根据此声明压栈,共20字节。
而函数实际定义为foo(char*, char *, void*),函数会把传入的第三个参数当做void*解析,16~19字节是整数0,20~23字节数据未定义,函数会取到一个错误的指针(在big-endian系统里,指针低位部分为0,高位部分未定义,即0x********00000000;在little-endian系统里,指针低位部分未定义,高位部分位0,即0x00000000********)。
字面量 0 ,是int型,在64位系统里是32位。
宏 NULL, 定义为((void*)0),在64位系统里是64位。
在foo函数定义之后使用该函数,或在使用foo函数之前声明一下foo函数,编译器就会自动将int型的0转为void*型的NULL。
如果没有定义或声明foo函数就直接使用foo函数,编译器会根据你传入的参数产生一个隐含的声明。
调用foo("foo", "bar", 0),编译器会认为你的函数声明为int foo(char*, char*, int),并且根据此声明压栈,共20字节。
而函数实际定义为foo(char*, char *, void*),函数会把传入的第三个参数当做void*解析,16~19字节是整数0,20~23字节数据未定义,函数会取到一个错误的指针(在big-endian系统里,指针低位部分为0,高位部分未定义,即0x********00000000;在little-endian系统里,指针低位部分未定义,高位部分位0,即0x00000000********)。
作者: krein8964 发布时间: 2010-10-16
相关阅读 更多
热门阅读
-
office 2019专业增强版最新2021版激活秘钥/序列号/激活码推荐 附激活工具
阅读:74
-
如何安装mysql8.0
阅读:31
-
Word快速设置标题样式步骤详解
阅读:28
-
20+道必知必会的Vue面试题(附答案解析)
阅读:37
-
HTML如何制作表单
阅读:22
-
百词斩可以改天数吗?当然可以,4个步骤轻松修改天数!
阅读:31
-
ET文件格式和XLS格式文件之间如何转化?
阅读:24
-
react和vue的区别及优缺点是什么
阅读:121
-
支付宝人脸识别如何关闭?
阅读:21
-
腾讯微云怎么修改照片或视频备份路径?
阅读:28