一、NULL指针、零指针
1、NULL指针
NULL是用来表示空指针常量。因此,可以用p = NULL;来使p成为一个空指针。
C++标准库定义的NULL指针:
/* Define NULL pointer value */
#ifndef NULL
#ifdef__cplusplus
#defineNULL 0
#else /* __cplusplus */
#define NULL ((void *)0)
#endif /* __cplusplus */
#endif /* NULL */
NULL是一个宏,在C++里面被直接定义成了0,而在没有__cplusplus定义的前提下,就被定义成了一个值是0的void* 类型指针常量,即:((void*)0)。
2、零指针
零指针是值为0的指针,可以是任何一种类型,可以是通用变体类型void*,也可以是int*、char*等。
在C++标准规定,当一个指针类型的数值是0时,认为这个指针是空的。
3、空指针指向了内存的什么地方(空指针的内部实现)?
标准并没有对空指针指向内存中的什么地方这一个问题做出规定,也就是说用哪个具体的地址值(0x00000000地址还是某一特定地址)表示空指针取决于系统的实现。我们常见的空指针一般指向0地址,即空指针的内部用全0来表示。在实际编程中,我们只需要了解一个指针是否是空指针就可以了。
4、空指针实现的保护政策
既然我们选择了0作为空的概念,在非法访问空的时候我们需要保护以及报错。因此,编译器和系统提供了很好的政策。
我们程序中的指针其实是WINDOWS内存段偏移后的地址,而不是实际的物理地址,所以不同的程序中的零值指针指向的同一个0地址,其实在内存中都不是物理内存的开端的0,而是分段的内存的开端,这里我们需要简单介绍一下WINDOWS下的内存分配和管理制度:
WINDOWS下,执行文件(PE文件)在被调用后,系统会分配给它一个额定大小的内存段用于映射这个程序的所有内容(就是磁盘上的内容)并且为这个段进行新的偏移计算,也就是说我们的程序中访问的所有NEAR指针都是在我们“自家”的段里面的,当我们要访问FAR指针的时候,我们其实是跳出了“自家的院子”到了他人的地方,我们需要一个段偏移地址来完成新的偏移(人家家里的偏移)所以我们的指针可能是OE02:0045就是告诉系统我们要访问0E02个内存段的0045好偏移,然后WINDOWS会自动给我们找到0E02段的开始偏移,然后为我们计算真实的物理地址。
所以程序A中的零值指针和程序B中的零值指针指向的地方可能是完全不同的。
保护政策:
我们的程序在使用的是系统给定的一个段,程序中的零值指针指向这个段的开端,为了保证NULL概念,系统为我们这个段的开头64K内存做了苛刻的规定,根据虚拟内存访问权限控制,我们程序中(低访问权限)访问要求高访问权限的这64K内存被视作是不容许的,所以会必然引发Access Volitation 错误,而这高权限的64K内存是一块保留内存(即不能被程序动态内存分配器分配,不能被访问,也不能被使用),就是简单的保留,不作任何使用。
我们在直接定义一个指针后并不知道这个指针指向何处(而不是有些程序员认为的如同JAVA等语言会自动零值初始化),所以我们一旦非法地直接访问这些未知地内容时,极其有可能会触碰到程序所不能触碰地内存(这时类似64K限制地保护政策又会起效,就如同你不仅随意闯入了陌生人的家(野指针),而且拿着刀子要问他要钱(访问),警察(WINDOWS内存访问保护政策)当然请你去警察局(报错)谈谈),所以养成良好的指针初始化(赋值为NULL)以及使用FREE(或者时DELETE)之后立即再初始化为空是十分必要的。
5、为什么通过空指针读写的时候就会出现异常?
NULL指针分配的分区:其范围是从0x00000000到0x0000FFFF。这段空间是空闲的,对于空闲空间而言,没有响应的物理存储器与之相对应,所以对这段空间来说,任何读写操作操作都是会引起异常的。空指针是程序无论在何时都没有物理存储器与之对应的地址。为了保障“无论何时”这个条件,需要人为划分一个空指针的区域,固有上面NULL指针分区。
6、是否可以定义自己的NULL的实现?
NULL是标准库中的一个reserved identifier(保留标识符)。所以,如果包含了相应的标准头文件而引入NULL的话,则再程序中重新定义NULL为不同的内容是非法的,其行为是未定义的。也就是说,如果是符合标准的程序,其NULL的值只能是0,不可能是除0之外的其它值,比如1、2、3等。
7、malloc函数在分配内存失败时返回0还是NULl?
malloc函数是标准C规定的库函数。在标准中明确规定了在其内存分配失败时返回的是一个“null pointer”(空指针)。对于空指针值,一般文档中倾向于用NULL表示,而没有直接说成0。但是我们应该清楚:对于指针类型来说,返回NULL和返回0是完全等价的,因为null和0都是表示“null pointer”(空指针)。
二、野指针
“野指针”不是NULL指针,是指向“垃圾”内存的指针。
1、产生“野指针”的主要情况
1) 指针变量没有被初始化。任何指针变量刚被创建时不会自动生成NULL指针,它的缺省值是随机的,它会乱指。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如:
char* p = NULL;
char* str = (char*)malloc(1000);
2) 指针p被free或delete之后,没有设置为NULL,让人误以为p是个合法的指针。free和delete只是把指针所指的内存给释放掉,但并没有把指针本身干掉。free以后其地址仍然不变(非NULL),只是该地址对应的内存是垃圾,p成了“野指针”。如果此时不把p设置为NULL,会让人误以为p是个合法的指针。如果程序比较长,我们有时记不住p所指向的内存是否已经被释放,在继续使用之前,通常会用语句if(p! = NULL)进行防错处理。很遗憾,此时的if语句达不到预期的作用,因为即便p不是NULl指针,它也不能指向合法的内存块。
例如:
#include"stdafx.h"
#include<stdio.h>
#include<iostream>
using namespace std;
#pragma warning(disable:4996);
void main()
{
char *p = (char*)malloc(100);
strcpy(p,"hello");
free(p); //p所指的内存被释放,但是p所指向的地址仍然不变
if (p != NULL) //没有起到放错作用
{
strcpy(p, "world");
}
printf("%s\n", p); //出错
getchar();
return;
}
3) 指针操作超越了变量的作用范围。
2015年5月16日