+ -
当前位置:首页 → 问答吧 → 64位环境 0和NULL 的区别

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 */);

作者: 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********)。

作者: krein8964   发布时间: 2010-10-16

热门下载

更多