最近在查看同事写的一段程序时,发现里边有一个函数大概如下:
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
《the c programming language》 page 92
还有一些分析没有做。
对于结构体指针变量为空时对其成员的访问是否异常?
曾经看到一个是说由编译器决定,
这个在自己的电脑上用gcc编译的程序,
访问时会出现异常,其他的编译器不知道会怎么样。