首页 » C语言解惑 » C语言解惑全文在线阅读

《C语言解惑》5.3 指针地址的有效性

关灯直达底部

1.地址的有效性

计算机的内存地址是有一定构成规律的。能被CPU访问的地址才是有效的地址,除此之外都是无效的地址。

【例5.7】找出下面程序中的错误。


#include <stdio.h>int main(){      char a=/'B/', *p;      int addr;      p=&a;       addr=&a;      printf(/"0x%p,0x%p,0x%pn/", &a,addr,p);      printf(/"%c,%c,%cn/",a,*p,*addr);        return 0;}  

这个程序与例2.14类似,只是变量为字符型并声明p为字符型指针。p是指针变量,存储的是地址,直接使用“p=&a”是天经地义。但addr不行,它是整型数值而&a是地址,两者不匹配,使用“addr=&a”就要给出警告信息。与例2.14的方法一样,使用


addr=(int)&a;  

即可完成匹配。同理,可以使用“*p”输出存储在p变量地址里的值,但不能使用“*addr”,因为addr是整数表示的地址。其实,它应该和p的类型一致,即使用“(char*)”转换一下。下面给出改正后的程序和运行结果。


#include <stdio.h>int main(){    char a=/'B/',*p;    int addr;    p=&a;     addr=(int)&a;    printf(/"0x%p,0x%p,0x%pn/", &a,addr,p);    printf(/"%c,%c,%cn/",a,*p,*(char*)addr);    return 0;}0x0012FF7C,0x0012FF7C,0x0012FF7CB,B,B  

也可以直接把地址赋给指针变量。以例5.7为例,p是字符型指针,所以要进行类型转换。*p是字符型,输出时无需转换,但addr需要使用“(char*)”转换,然后再对它使用“*”号输出字符值。详见下面的例子,输出结果与例5.7相同。

【例5.8】直接赋地址值的例子。


#include <stdio.h>int main(){      char a=/'B/',*p;      int addr;      addr=(int)0x0012FF7C;      p=(char *)0x0012FF7C;      printf(/"0x%p, 0x%p, 0x%pn/", &a,addr,p);      printf(/"%c,%c,%cn/",a,*p,*(char*)addr);      return 0;}  

这里给p赋的地址值是存储字符B的地址值,这个地址是有效的。由此可见,可以随便把一个地址赋给p,只要转换匹配一下即可,p是来者不拒,并不管给它赋的什么值,更不管其后果。声明一个指针,必须赋给它一个合理的地址值,请看下面的例子。

【例5.9】演示给指针赋有效和无效地址的例子。


#include <stdio.h>int main(){        char *p,a=/'A/',b=/'B/';        p=&a;        printf(/"0x%p, %cn/", p,*p);        p=(char *)0x0012FF74;        printf(/"0x%p, %cn/", p,*p);        p=(char *)0x0012FF78;        printf(/"0x%p, %cn/", p,*p);        p=(char *)0x0012FF7C;        printf(/"0x%p, %cn/", p,*p);        p=(char *)0x1234;        printf(/"0x%pn/", p);        printf(/"%cn/", *p);        return 0;}  

编译正确,但运行时出现异常。


0x0012FF78, A0x0012FF74, B0x0012FF78, A0x0012FF7C, |0x00001234  

当指针赋予字符A的地址时,指针地址不仅有效,且*p具有确定的字符A。当将p改赋地址0x0012FF74时,这个地址恰恰是系统分给字符B的地址,这个地址不仅有效,且*p具有确定的字符B。地址有效,但内容不一定确定。0x0012FF7C是有效地址,但程序没有使用这个地址,所以决定不了它的内容,输出字符“|”是无法预知的。地址0x1234虽然能被指针p接受,也能输出这个地址,但这个地址是无效的,所以执行语句


printf(/"%cn/", *p);  

时,产生运行时错误。也就是当赋一个无效的地址给p时,就不能对*p操作。

结论:使用指针必须对其初始化,必须给指针赋予有效的地址。

2.指针本身的可变性

编译系统为变量分配的地址是不变的,为指针变量分配的地址也是如此,但指针变量所存储的地址是可变的。

【例5.10】有如下程序:


#include <stdio.h>int main(){      int a=15, b=25, c=35,i=0;                    //4      int *p=&a;                              //5      printf(/"0x%p,0x%p,0x%pn/", &a,&b,&c);          //6      printf(/"0x%p,0x%p,0x%p,%dn/", &p,*&p,p,*p);     //7      for(i=0;i<3;i++,p--)                    //8           printf(/"%d /", *p);                    //9      printf(/"n%d,0x%p,0x%pn/", *p,p,&p);          //10      for(i=0,++p;i<3;i++,p++)                    //11           printf(/"%d /", *p);                    //12      printf(/"n%d,0x%p,0x%pn/", *p,p,&p);          //13      --p;                                   //14      for(i=0;i<3;i++)                         //15           printf(/"%d /", *(p-i));                    //16           printf(/"n%d,0x%p,0x%pn/", *p,p,&p);          //17      for(i=0;i<3;i++)                         //18           printf(/"%d /", *(p-2+i));               //19      printf(/"n%d,0x%p,0x%pn/", *p,p,&p);          //20      return 0;} 

假设运行后,5、6两行给出如下输出信息。


0x0012FF7C,0x0012FF78,0x0012FF740x0012FF6C,0x0012FF7C,0x0012FF7C,15  

请问能分析出程序后面的输出结果吗?

【解答】因为有两个地址里存储的值不是由程序决定的,所以有两个输出不能确定。除此之外,其他的输出值均可以根据给出的输出语句,写出确定的输出结果。

为了便于分析,首先要清楚所给上述输出结果的含义。

(1)从第一行可知,这依次是分配给变量a,b,c的地址。

(2)a的地址是0x0012FF7C。注意第2行的输出中,第3个和第4个的值与它相等。

(3)第1行第1个0x0012FF6C对应“&p”,是编译系统为指针分配的地址,用来存放指针p。因为已经给指针变量赋值(p=&a),所以“*&p”就是输出指针地址0x0012FF6C里的内容0x0012FF7C。它就是p指向a的地址,即p也输出0x0012FF7C。也就是说,*&p,p,&a三个的值相同。

(4)“*p”就是输出指针p指向地址0x0012FF7C里所存储的变量a的值,即15。

要分析输出,需要掌握如下操作含义。

(1)编译系统为声明的变量a分配存储地址,运行时可以改变a的数值,但不会改变存储a的地址,即&a的地址值不变。同理,为声明的指针变量p分配一个存储地址,p指向的地址值可以变化,但&p的地址不会变化。

(2)可以对指针变量p做加、减操作。由第1行输出结果知,p=p-1(可记做--p),则p指向的地址是0x0012FF78,*p输出字符B,再执行p--,则*p输出C。如果再执行p++,则*p输出C。这时,对p操作后,不仅p指向的地址有效,其地址中存储的内容也正确。

(3)如果p的操作超出这三个变量的地址,就无法得出输出结果。

按照上述提示,预测如下。

(1)第8~10行中的for语句就是输出三个变量的值(15 25 35),输出完之后,可以预测p指向地址为0x0012FF70,但不能预测*p的内容。运行过程中&p保持为0x0012FF6C。

(2)第11~13行中的for语句是反向输出三个变量的值(35 25 15),输出完之后,可以预测p指向地址为0x0012FF80,但不能预测*p的内容。&p仍为0x0012FF6C。

(3)第14~17行中的for语句也是输出三个变量的值(15 25 35),第14行将p调整指向存储a的地址,循环语句中使用“*(p-i)”,因为只是使用p做基准,用i做偏移量,所以p的值不变,输出完之后,p不变,仍为0x0012FF7C,*p=15,&p不变。

(4)第18~20行中的for语句是反向输出三个变量的值(35 25 15),循环语句也使用p做基准中,即“*(p-2+i)”。输出完之后,p不变,仍为0x0012FF7C,*p=15,&p不变。

由此可见,要小心对p的操作,以免进入程序非使用区或无效地址。如果使用不当,严重时会使系统崩溃,这是使用指针的难点之一。

程序实际运行结果如下。


0x0012FF7C,0x0012FF78,0x0012FF740x0012FF6C,0x0012FF7C,0x0012FF7C,1515 25 353,0x0012FF70,0x0012FF6C35 25 151245120,0x0012FF80,0x0012FF6C15 25 3515,0x0012FF7C,0x0012FF6C35 25 1515,0x0012FF7C,0x0012FF6C  

3.没有初始化指针和空指针

【例5.11】没有初始化指针与初始化为空指针的区别。


#include <stdio.h>int main(){        int a=15;        int *p;        printf(/"指针没有初始化:n/", p,&p);        printf(/"0x%p,0x%pn/", p,&p);        p=NULL;        printf(/"指针初始化为NULL:n/", p,&p);        printf(/"0x%p,0x%pn/", p,&p);}  

运行结果如下。


指针没有初始化:0xCCCCCCCC,0x0012FF78指针初始化为NULL:0x00000000,0x0012FF78  

显然,这两种情况下,不管如何初始化指针,&p分配的地址是一样的,区别是指针变量存放的值。

指针在没有初始化之前,指针变量没有存储有效地址,如果对“*p”进行操作,就会产生运行时错误。当用NULL初始化指针时,指针变量存储的内容是0号地址单元,这虽然是有效的地址,但也不允许使用“*p”,因为这是系统地址,不允许应用程序访问。

为了用好指针,应养成在声明指针时,就予以初始化。既然初始化为NULL,也会产生运行时错误,何必要选择这种初始化方式呢?其实,这是为了为程序提供一种判断依据。例如,申请一块内存块,在使用之前要判断是否申请成功(申请成功才能使用)。


int *p=NULL;p=(int*)malloc(100);if(p==NULL);{    printf(/"内存分配错误!n/");    exit(1);     //结束运行} 

注意正确地包含必要的头文件,下面给出一个完整的例子。

【例5.12】判断空指针的完整例子。


#include <stdlib.h>#include <stdio.h>void main( ){   char *p;   if( (p = (char *)malloc(100) ) == NULL) {       printf(/"内存不够!n/");       exit(1);   }   gets(p);   printf(/"%sn/", p);   free(p);}  

其实,只要控制住指针的指向,使用中就可避免出错。

把它与整型变量对比一下,就容易理解指针的使用。整型变量存储整型类型数据的变量,也就是存储规定范围的整数。指针变量存储指针,也就是存储表示地址的正整数。由此可见,一个指针变量的值就是某个内存单元的地址,或称为某个内存单元的指针。可以讲:指针的概念就是地址。

由此可见,通过指针可以对目标对象进行存取(*操作符),故又称指针指向目标对象。指针可以指向各种基本数据类型的变量,也可以指向各种复杂的导出数据类型的变量,如指向数组元素。