本文将介绍如何对NULL指针地址建立合法映射,从而合法访问NULL指针。本文表达的宗旨:
任何虚拟地址,只要有合法的页表映射,就能访问!
提到C语言编程,我想几乎所有人都遭遇过NULL指针。我们的代码中总是在不断的判断指针是否为NULL:
if (p1 != NULL) { //...} if (p2 == NULL) { exit(-1);}
如果我们忘记了这种判断,我们会收获到段错误:
[15445.731305] a.out[3511]: segfault at 0 ip 000000000040071c sp 00007ffedbacbdd0 error 4 in a.out[400000+1000]
诚然,我们都讨厌segfault,但segfault并非由于访问NULL指针引起的,相反,我们要感谢NULL指针,它帮助我们的程序排除了大量的segfault。
在现代操作系统中,程序访问的地址都是虚拟地址,硬件MMU结合操作系统创建的页表会在进程私有虚拟地址和全局物理地址之间做映射,当程序访问一个虚拟地址的时候,该映射会将这次访问转换成到物理地址的访问。
所以,segfault的本质是程序访问的虚拟内存地址无法合理映射到物理地址的一种错误通知。
引发segfault的地址成为非法地址。
现在,随意给出两个虚拟地址:
unsigned char *p1 = 0x7f1233443344;unsigned char *p2 = 0xaa12bb443344;
谁能说出哪个虚拟地址是合法的,哪个是非法的?谁也说不出,只有试着访问它的时候才知道,引发segfault的地址就是非法的,否则就是合法的。这可能会对程序数据造成严重的伤害。
因此有必要人为规定一个非法地址,这样在程序中就可以做判断了,只要不是人为规定的那个非法地址,那就是合法的。至于说谁来严格保证其合法性,除了需要编程规范和编程习惯之外,操作系统也确实不会为该非法地址映射可以访问的物理页面。有法可依只是安全的必要条件,加上违法必究才是充分且必要的。
数字0是最特殊的,判断一个值是否为0在硬件层面上也很高效,把0作为非法地址具有高度的可辨识性,于是几乎所有的编程语言都用0来表示非法地址:
#define NULL 0