最近在查看同事写的一段程序时,发现里边有一个函数大概如下: void example(uint8 *pData) { ... if(NULL == *pData) return; while(*pData != NULL) { ... } ... } 第一眼看去感觉红色部分写的没有什么,当看到while时, 感觉第一句有些多余, 没有必要这样判断,当*pData == NULL时while就进不去, 但是想到这里是第一次判断pData,于是估计当时同事是想写if(NULL == pData),笔误写作了if(NULL == *pData). 于是我跟同事提出这里是一个bug,有可能会造成程序读取NULL指针,引发异常。 同事看后,说没事,就是判断*pData == NULL。 如果说是判断*pData,就没有必要用第一个if。 最早理解的是不能对NULL指针进行操作,所以要判断pData非空。 因为最早是在《程序员成长计划》中,看到对于传入指针参数的非NULL判断的重要性,同时向NULL指针写入数据会出错的。 所以我建议最好不要这样操作。 同事说,NULL指针怎么就不能读呢? 这个问题确实难倒了我,确实如果NULL指针允许读,也就不会异常了。(现在想到,这也是不可能的,虽然程序没有引发异常,但是这是在NULL处开始读值,读出的值也会影响操作的,因为这个指针,如果是正常操作,不会传入NULL进来,这比直接异常还要可怕,因为将bug的地方引到了别处,反而会提高找出bug的难度。还不如直接异常,下边关于结构的的NULL就有这个问题) 对于在MCU中,虽然没有操作系统的管理,但是对于NULL处的一般是存放中断向量表的起始地址的地方,尤其是上电复位的第一个跳转指令。所以这一段地址,程序读取也是没有意义的,所以对于C语言使用判断指针为NULL,也是可以的。但是会不会引发异常,我不确定,但是至少会造成程序运行不正常。 空指针在C/C++中占有特殊的地址,通常它是用来判断一个指针的有效性的。空指针一般定义为0,。现代操作系统都会保留从0开始的一块内存,至于这块内存有多大,是不同的操作系统而定。一旦程序试图访问这块内存,系统就会触发一个异常/信号。 操作系统为什么要保留一块内存,而不是仅仅保留一个字节的内存呢?这是因为一般内存管理都是按页进行管理的,根本就无法仅仅保留一个字节, 而至少要保留一个页面。保留一块内存也有额外的好处,可以检查注入p=NULL,p[1]之类的内存错误。 在一些嵌入式系统中(如ARM7,Cortex-M3),从零开始的一块内存是用来安防中断向量的,没有MMU的保护,直接访问这块内存好像不会引发异常。不过这块内存是代码的,而不是程序中有效变量的地址,所以用空指针来判断指针的有效性任然是可行的。 《程序员成长计划》写的又好又快的秘诀。 不过, 对于在Linux系统下,关于NULL指针肯定是不允许读的,因为操作系统的text端起始于0x08048000,对于0~0x08048000之间的约128M空间,用来判断NULL指针内容。 在Linux系统下,IA-32系统起始于0x08048000(ARM9内核系统中是0x8000),在text段的起始地址与最低的可用地址之间有大约128M的间距,用于捕获NULL指针。不允许用户空间访问。 《深入Linux内核架构》 NULL指针的定义 Pointers and integers are not interchangeable. Zero is the sole exception: the constant zero may be assigned to a pointer, and a pointer may be compared with the constant zero. The symbolic constant NULL is often used in place of zero, as a mnemonic to indicate more clearly that this is a special value for a pointer. NULL is defined in <stdio.h>. 《the c programming language》 page 92 还有一些分析没有做。 对于结构体指针变量为空时对其成员的访问是否异常? 曾经看到一个是说由编译器决定, 这个在自己的电脑上用gcc编译的程序, 访问时会出现异常,其他的编译器不知道会怎么样。
时间: 2024-10-27 14:03:47