Chinaunix首页 | 论坛 | 博客
  • 博客访问: 139786
  • 博文数量: 38
  • 博客积分: 306
  • 博客等级: 二等列兵
  • 技术积分: 335
  • 用 户 组: 普通用户
  • 注册时间: 2012-10-29 15:19
文章分类

全部博文(38)

文章存档

2013年(23)

2012年(15)

我的朋友

分类: C/C++

2012-11-15 17:40:15

指针的内存布局

先看下面的例子:

int *p;

在32 位系统下,不管什么样的指针类型,其大小都为4byte。可以测试一下sizeof(void *)。

int *p = NULL 和*p = NULL 有什么区别?

我们先看下面的代码:

int *p = NULL;

这时候我们可以通过编译器查看p 的值为0x00000000。这句代码的意思是:定义一个指针

变量p,其指向的内存里面保存的是int 类型的数据;在定义变量p 的同时把p 的值设置为

0x00000000,而不是把*p 的值设置为0x00000000。这个过程叫做初始化,是在编译的时候

进行的。

明白了什么是初始化之后,再看下面的代码:

int *p;

*p = NULL;

同样,我们可以在编译器上调试这两行代码。第一行代码,定义了一个指针变量p,其指向

的内存里面保存的是int 类型的数据;但是这时候变量p 本身的值是多少不得而知,也就是

说现在变量p 保存的有可能是一个非法的地址。第二行代码,给*p 赋值为NULL,即给p

指向的内存赋值为NULL;但是由于p 指向的内存可能是非法的,所以调试的时候编译器可

能会报告一个内存访问错误。这样的话,我们可以把上面的代码改写改写,使p 指向一块合

法的内存:

int i = 10;

int *p = &i;

*p = NULL;

在编译器上调试一下,我们发现p 指向的内存由原来的10 变为0 了;而p 本身的值, 即内

存地址并没有改变。

经过上面的分析,相信你已经明白它们之间的区别了。不过这里还有一个问题需要注

意,也就是这个NULL。初学者往往在这里犯错误。

注意NULL 就是NULL,它被宏定义为0

#define NULL 0

很多系统下除了有NULL外,还有NUL(Visual C++ 6.0 上提示说不认识NUL)。NUL 是ASCII

码表的第一个字符,表示的是空字符,其ASCII 码值为0。其值虽然都为0,但表示的意思

完全不一样。同样,NULL 和0 表示的意思也完全不一样。一定不要混淆。

如何将数值存储到指定的内存地址

假设现在需要往内存0x12ff7c 地址上存入一个整型数0x100。我们怎么才能做到呢?我

们知道可以通过一个指针向其指向的内存地址写入数据,那么这里的内存地址0x12ff7c 其

本质不就是一个指针嘛。所以我们可以用下面的方法:

int *p = (int *)0x12ff7c;

*p = 0x100;

需要注意的是将地址0x12ff7c 赋值给指针变量p 的时候必须强制转换

 

编译器的bug?

另外一个有意思的现象,在Visual C++ 6.0 调试如下代码的时候却又发现一个古怪的问

题:

int *p = (int *)0x12ff7c;

*p = NULL;

p = NULL;

在执行完第二条代码之后,发现p 的值变为0x00000000 了。按照我么上一节的解释,应该p

的值不变,只是p 指向的内存被赋值为0。难道我们讲错了吗?别急,再试试如下代码:

int i = 10;

int *p = (int *)0x12ff7c;

*p = NULL;

p = NULL;

通过调试,发现这样子的话,p 的值没有变,而p 指向的内存的值变为0 了。这与我们

前面讲解的完全一致。当然这里的i 的地址刚好是0x12ff7c,但这并不能改变“*p = NULL;”

这行代码的功能。

为了再次测试这个问题,我又调试了如下代码:

int i = 10;

int j = 100;

int *p = (int *)0x12ff78;

*p = NULL;

p = NULL;

这里0x12ff78 刚好就是变量j 的地址。这样的话一切正常,但是如果把“int j = 100;

”这行代码删除的话,又出现上述的问题了。测试到这里我还是不甘心,编译器怎么能犯这

种低级错误呢?于是又接着进行了如下测试:

unsigned int i = 10;

//unsigned int j = 100;

unsigned int *p = (unsigned int *)0x12ff78;

*p = NULL;

p = NULL;

得到的结果与上面完全一样。当然,我还是没有死心,又进行了如下测试:

char ch = 10;

char *p = (char *)0x12ff7c;

*p = NULL;

p = NULL;

这样子的话,完全正常。但当我删除掉第一行代码后再测试,这里的p 的值并未变成

0x00000000,而是变成了0x0012ff00,同时*p 的值变成了0。这又是怎么回事呢?初学者是

否认为这是编译器“良心发现”,把*p 的值改写为0 了。

如果你真这么认为,那就大错特错了。这里的*p 还是地址0x12ff7c 上的内容吗?显然

不是,而是地址0x0012ff00 上的内容。至于0x12ff7c 为什么变成0x0012ff00,则是因为编

译器认为这是把NULL 赋值给char 类型的内存,所以只是把指针变量p 的低地址上的一个

字节赋值为0。至于为什么是低地址,请参看前面讲解过大小端模式相关内容。

测试到这里,已经基本可以肯定这是Visual C++ 6.0 的一个bug所以平时一定不要迷

信某个编译器,要相信自己的判断

数组的内存布局

先看下面的例子:

int a[5];


sizeof(a[5])的值在32 位系统下为4。并没有出错,为什么呢?我们讲过sizeof 是关键字不是函数。函数求值是在运行的时候,而关键字sizeof求值是在编译的时候。虽然并不存在a[5]这个元素,但是这里也并没有去真正访问a[5],而是仅仅根据数组元素的类型来确定其值。所以这里使用a[5]并不会出错

    sizeof(&a)的值在32 位系统下也为4,这也很好理解。取数组a 的首地址。但是在Visual C++6.0 上,这个值为20,我认为是错误的。

    a 作为右值时其意义与&a[0]是一样,代表的是数组首元素的首地址,而不是数组的首地址


省政府和市政的区别----&a[0]和&a 的区别

这里&a[0]和&a 到底有什么区别呢?a[0]是一个元素,a 是整个数组,虽然&a[0]和&a

的值一样,但其意义不一样。前者是数组首元素的首地址,而后者是数组的首地址。举个

例子:湖南的省政府在长沙,而长沙的市政府也在长沙。两个政府都在长沙,但其代表的

意义完全不同。这里也是同一个意思。

 

指针与数组

2.数组和指针

A),char *p = “abcdef”;

B),char a[] = “123456”;

比如现在需要读取字符‘e’,我们有两种方式:

1),以指针的形式:*(p+4)。先取出p里存储的地址值,假设为0x0000FF00,然后加上4个字符的偏移量,得到新的地址0x0000FF04。然后取出0x0000FF04 地址上的值。

2),以下标的形式:p[4]。编译器总是把以下标的形式的操作解析为以指针的形式的操

。p[4]这个操作会被解析成:先取出p 里存储的地址值,然后加上中括号中4个元素的偏

移量,计算出新的地址,然后从新的地址中取出值。也就是说以下标的形式访问在本质上

与以指针的形式访问没有区别,只是写法上不同罢了。

    另外一个需要强调的是:上面所说的偏移量4代表的是4个元素,而不是4 个byte。只

不过这里刚好是char 类型数据1 个字符的大小就为1 个byte。记住这个偏移量的单位是元

素的个数而不是byte 数,在计算新地址时千万别弄错了。

    例子A)定义了一个指针变量p,p 本身在栈上占4 个byte,p 里存储的是一块内存的首

地址。这块内存在静态区,其空间大小为7 个byte,这块内存也没有名字

    例子B)定义了一个数组a,a 拥有7 个char 类型的元素,其空间大小为7。数组a 本身

上面


a 和&a 的区别

通过上面的分析,相信你已经明白数组和指针的访问方式了,下面再看这个例子:

main()

{

       inta[5]={1,2,3,4,5};

       int *ptr=(int*)(&a+1);

       printf("%d,%d",*(a+1),*(ptr-1));

}

    对指针进行加1操作,得到的是下一个元素的地址,而不是原有地址值直接加1。所以,一个类型为T 的指针的移动,以sizeof(T) 为移动单位。因此,对上题来说,a 是一个一维数组,数组中有5 个元素;ptr 是一个int 型的指针。

&a + 1: 取数组a 的首地址,该地址的值加上sizeof(a) 的值,即&a + 5*sizeof(int),也

就是下一个数组的首地址,显然当前指针已经越过了数组的界限。

(int *)(&a+1): 则是把上一步计算出来的地址,强制转换为int * 类型,赋值给ptr。

*(a+1): a,&a 的值是一样的,但意思不一样,a 是数组首元素的首地址,也就是a[0]的首地址,&a 是数组的首地址,a+1 是数组下一元素的首地址,即a[1]的首地址,&a+1 是下一个数组的首地址。所以输出2

*(ptr-1): 因为ptr 是指向a[5],并且ptr 是int * 类型,所以*(ptr-1)是指向a[4] ,

输出5。
  1. char a[4]="123";  
  2.     char *cp=a;  
  3.   
  4.     int b[4]={1,2,3,4};  
  5.     int *ip=b;  
  6.   
  7.     int c[4]={2,3,4,5};       
  8.   
  9. /*对指针进行加1操作,得到的是下一个元素的地址,而不是原有地址值直接加1 
  10. */  
  11.   
  12.     printf("%d\n%d\n",cp,cp+1); //值:1244996 其中cp指向char类型   
  13.                                 //   1244997   
  14.   
  15.     printf("%d\n%d\n",ip,ip+1);//值:1244976  其中ip指向int类型   
  16.                                //    1244980   
  17.                                   
  18. /*指针在32位系统中的分配为4个字节! 
  19. */  
  20.     printf("%d\n%d\n",&cp,&cp+1);//值:   1244992   
  21.                                  //值:   1244996   
  22.   
  23.     printf("%d\n%d\n",&ip,&ip+1);//值:   1244972   
  24.                                  //     1244976   
  25.   
  26. /*c是数组首元素的首地址,&c[0]是数组首元素的首地址,&a是数组首地址。 
  27.     三个值是一样的。前2者的意义相同,第3个意义与前两个不同。 
  28. */  
  29.     printf(%d\n%d\n%d\n",c,&c[0],&c);  
  30.     printf("%d\n%d\n%d\n",c+1,&c[0]+1,&c+1);    //1244960   
  31.                                                 //1244960   
  32.                                                 //1244972  
char a[4]="123"; char *cp=a; int b[4]={1,2,3,4}; int *ip=b; int c[4]={2,3,4,5}; /*对指针进行加1操作,得到的是下一个元素的地址,而不是原有地址值直接加1 */ printf("%d\n%d\n",cp,cp+1); //值:1244996 其中cp指向char类型 // 1244997 printf("%d\n%d\n",ip,ip+1);//值:1244976 其中ip指向int类型 // 1244980 /*指针在32位系统中的分配为4个字节! */ printf("%d\n%d\n",&cp,&cp+1);//值: 1244992 //值: 1244996 printf("%d\n%d\n",&ip,&ip+1);//值: 1244972 // 1244976 /*c是数组首元素的首地址,&c[0]是数组首元素的首地址,&a是数组首地址。 三个值是一样的。前2者的意义相同,第3个意义与前两个不同。 */ printf(%d\n%d\n%d\n",c,&c[0],&c); printf("%d\n%d\n%d\n",c+1,&c[0]+1,&c+1); //1244960 //1244960 //1244972


 

 指针和数组的定义与声明

定义为数组,声明为指针

 文件1 中定义如下:
char a[100];
文件2 中声明如下(关于extern 的用法,以及定义和声明的区别,请复习第一章):
extern char *a;

当你声明为extern char *a 时,编译器理所当然的认为a 是一个指针变量,在32 位系统下,占4 个byte。这4 个byte 里保存了一个地址,这个地址上存的是字符类型数据。虽
然在文件1 中,编译器知道a 是一个数组,但是在文件2 中,编译器并不知道这点。大多数编译器是按文件分别编译的,编译器只按照本文件中声明的类型来处理。所以,虽然a 实际大小为100 个byte,但是在文件2 中,编译器认为a 只占4 个byte。

定义为指针,声明为数组:显然,按照上面的分析,我们把文件1 中定义的数组在文件2 中声明为指针会发生错误。同样的,如果在文件1 中定义为指针,而在文件中声明为数组也会发生错误

 

指针: 间接访问数据,首先取得指针变量p 的内容,把它作为地址,然后从这个地址提取数据或向这个地址写入数据。指针可以以指针的形式访问*(p+i);也可以以下标的形式访问p[i]。但其本质都是先取p 的内容然后加上i*sizeof(类型)个byte 作为数据的真正地址

            通常用于动态数据结构

            相关的函数为malloc 和free。

            通常指向匿名数据(当然也可指向具名数据)

数组: 直接访问数据,数组名a 是整个数组的名字,数组内每个元素并没有名字。只能通过“具名+匿名”的方式来访问其某个元素,不能把数组当一个整体来进行读写操作。数组可以以指针的形式访问*(a+i);也可以以下标的形式访问a[i]。但其本质都是a 所代表的数组首元素的首地址加上i*sizeof(类型)个byte 作为数据的真正地址。

            通常用于存储固定数目且数据类型相同的元素。

            隐式分配和删除

            自身即为数组名

指针数组和数组指针

A),int *p1[10];
B),int (*p2)[10];
“[]”的优先级比“*”要高。p1 先与“[]”结合,构成一个数组的定义,数组名为p1,int *
修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含10 个
指向int 类型数据的指针,即指针数组。至于p2 就更好理解了,在这里“()”的优先级比
“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,
即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚p2 是一个指
针,它指向一个包含10 个int 类型数据的数组,即数组指针

 再论a 和&a 之间的区别
既然这样,那问题就来了。前面我们讲过a 和&a 之间的区别,现在再来看看下面的代
码:
int main()
{
char a[5]={'A','B','C','D'};
char (*p3)[5] = &a;
char (*p4)[5] = a;

printf("%d\n",p3); //1244992
printf("%d\n",*p3); //两个值是一样的,说明存在p3中的是数组a的首地址,若想取首元素:**p3或者*p3[0]

printf("%d\n",p3+1); //1244997
printf("%d\n",*(p3+1));//1244997

printf("%d\n",p4);  //1244992
printf("%d\n",p4+1); //1244997
return 0;
}
上面对p3 和p4 的使用,哪个正确呢?p3+1 的值会是什么?p4+1 的值又会是什么?
毫无疑问,p3 和p4 都是数组指针,指向的是整个数组。&a 是整个数组的首地址,a
是数组首元素的首地址,其值相同但意义不同。在C 语言里,赋值符号“=”号两边的数据
类型必须是相同的,如果不同需要显示或隐式的类型转换。p3 这个定义的“=”号两边的数
据类型完全一致,而p4 这个定义的“=”号两边的数据类型就不一致了。左边的类型是指
向整个数组的指针,右边的数据类型是指向单个字符的指针。在Visual C++6.0 上给出如下
警告:warning C4047: 'initializing' : 'char (*)[5]' differs in levels of indirection from 'char *'。还好,
这里虽然给出了警告,但由于&a 和a 的值一样,而变量作为右值时编译器只是取变量的值
所以运行并没有什么问题。不过我仍然警告你别这么用。
既然现在清楚了p3 和p4 都是指向整个数组的,那p3+1 和p4+1 的值就很好理解了。
但是如果修改一下代码,会有什么问题?p3+1 和p4+1 的值又是多少呢?
int main()
{
char a[5]={'A','B','C','D'};
char (*p3)[3] = &a;
char (*p4)[3] = a;
return 0;
}
甚至还可以把代码再修改:
int main()
{
char a[5]={'A','B','C','D'};
char (*p3)[10] = &a;
char (*p4)[10] = a;
return 0;
}
这个时候又会有什么样的问题?p3+1 和p4+1 的值又是多少?

地址的强制转换
先看下面这个例子:
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
假设p 的值为0x100000。如下表表达式的值分别为多少?
p + 0x1 = 0x___ ?
(unsigned long)p + 0x1 = 0x___?
(unsigned int*)p + 0x1 = 0x___?
我相信会有很多人一开始没看明白这个问题是什么意思。其实我们再仔细看看,这个知识点
似曾相识。一个指针变量与一个整数相加减,到底该怎么解析呢?
还记得前面我们的表达式“a+1”与“&a+1”之间的区别吗?其实这里也一样。指针变
量与一个整数相加减并不是用指针变量里的地址直接加减这个整数。这个整数的单位不是
byte 而是元素的个数。
所以:
p + 0x1 的值为0x100000+sizof(Test)*0x1。至于此结构体的大小为20byte,前面的章
节已经详细讲解过。所以p +0x1 的值为:0x100014。
(unsigned long)p + 0x1 的值呢?这里涉及到强制转换,将指针变量p 保存的值强制转换
成无符号的长整型数。任何数值一旦被强制转换,其类型就改变了。所以这个表达式其实就
是一个无符号的长整型数加上另一个整数。所以其值为:0x100001。
(unsigned int*)p + 0x1 的值呢?这里的p 被强制转换成一个指向无符号整型的指针。所
以其值为:0x100000+sizof(unsigned int)*0x1,等于0x100004。

例子:

intmain()
{
int a[4]={1,2,3,4};
int *ptr1=(int *)(&a+1);
int *ptr2=(int *)((int)a+1);
printf("%x,%x",ptr1[-1],*ptr2);
return 0;
}

ptr1:将&a+1 的值强制转换成int*类型,赋值给int* 类型的变量ptr,ptr1 肯定指到数
组a 的下一个int 类型数据了。ptr1[-1]被解析成*(ptr1-1),即ptr1 往后退4 个byte。所以其
值为0x4。
ptr2:(int)a+1 的值是元素a[0]的第二个字节的地址。然后把这个地址
强制转换成int*类型的值赋给ptr2,也就是说*ptr2 的值应该为元素a[0]的第二个字节开始的
连续4 个byte 的内容

这连续4 个byte 里到底存了什么东西呢?也就是说元素a[0],a[1]里面
的值到底怎么存储的。这就涉及到系统的大小端模式了,如果懂汇编的话,这根本就不是问
题。既然不知道当前系统是什么模式,那就得想办法测试。我们可以用
下面这个函数来测试当前系统的模式。
int checkSystem( )
{
union check
{
int i;
char ch;
} c;
c.i = 1;
return (c.ch ==1);
}

如果当前系统为大端模式这个函数返回0;如果为小端模式,函数返回1。
也就是说如果此函数的返回值为1 的话,*ptr2 的值为0x2000000。
如果此函数的返回值为0 的话,*ptr2 的值为0x100。

二维数组

char a[3][4];

以数组下标的方式来访问其中的某个元素:a[i][j]。编译器总是将二维数组看成是一个
一维数组,
而一维数组的每一个元素又都是一个数组。a[3]这个一维数组的三个元素分别为:
a[0],a[1],a[2]。每个元素的大小为sizeof(a[0]),即sizof(char)*4。由此可以计算出a[0],a[1],a[2]
三个元素的首地址分别为& a[0],& a[0]+ 1*sizof(char)*4,& a[0]+ 2*sizof(char)*4。亦即a[i]
的首地址为& a[0]+ i*sizof(char)*4。这时候再考虑a[i]里面的内容。就本例而言,a[i]内有4
个char 类型的元素,其每个元素的首地址分别为&a[i],&a[i]+1*sizof(char),
&a[i]+2*sizof(char),&a[i]+3*sizof(char),即a[i][j]的首地址为&a[i]+j*sizof(char)。再把&a[i]
的值用a 表示,得到a[i][j]元素的首地址为:a+ i*sizof(char)*4+ j*sizof(char)。同样,可以换
算成以指针的形式表示:*(*(a+i)+j)。


#include
int main(int argc,char * argv[])
{
int a [3][2]={(0,1),(2,3),(4,5)};
int *p;
p=a [0];
printf("%d",p[0]);
}
问打印出来的结果是多少?

花括号里面嵌套的是小括号,而不是花括号!这里是花括号里面嵌套了逗号表达式!

其实这个赋值就相当于int a [3][2]={ 1, 3,5};

 所以,在初始化二维数组的时候一定要注意,别不小心把应该用的花括号写成小括号
了。

&p[4][2] - &a[4][2]的值为多少?

int a[5][5];
int (*p)[4];
p = a;
问&p[4][2] - &a[4][2]的值为多少?
这个问题似乎非常简单,但是几乎没有人答对了。我们可以先写代码测试一下其值,然后分
析一下到底是为什么。在Visual C++6.0 里,测试代码如下:
intmain()
{
int a[5][5];
int (*p)[4];
p = a;
printf("a_ptr=%#p,p_ptr=%#p\n",&a[4][2],&p[4][2]);
printf("%p,%d\n",&p[4][2] - &a[4][2],&p[4][2] - &a[4][2]);
return 0;
}
经过测试,可知&p[4][2] - &a[4][2]的值为-4。这到底是为什么呢?下面我们就来分析一下:
前面我们讲过,当数组名a 作为右值时,代表的是数组首元素的首地址。这里的a 为二
维数组,我们把数组a 看作是包含5 个int 类型元素的一维数组,里面再存储了一个一维数组。
如此,则a 在这里代表的是a[0]的首地址。a+1 表示的是一维数组a 的第二个元素。a[4]表
示的是一维数组a 的第5 个元素,而这个元素里又存了一个一维数组。所以&a[4][2]表示的
是&a[0][0]+4*5*sizeof(int) + 2*sizeof(int)。
根据定义,p 是指向一个包含4 个元素的数组的指针。也就是说p+1 表示的是指针p 向
后移动了一个“包含4 个int 类型元素的数组”。这里1 的单位是p 所指向的空间,即
4*sizeof(int)。所以,p[4]相对于p[0]来说是向后移动了4 个“包含4 个int 类型元素的数组”,
即&p[4]表示的是&p[0]+4*4*sizeof(int)。由于p 被初始化为&a[0],那么&p[4][2]表示的是
&a[0][0]+4*4*sizeof(int)+2* sizeof(int)。

再由上面的讲述,&p[4][2] 和&a[4][2]的值相差4 个int 类型的元素

解决这类问题的最好办法就是画内存布局图

二级指针

例如:
char **p;
定义了一个二级指针变量p。p 是一个指针变量,毫无疑问在32 位系统下占4 个byte。
它与一级指针不同的是,一级指针保存的是数据的地址,二级指针保存的是一级指针的地
址。

我们试着给变量p 初始化:
A),p = NULL;
B),char *p2; p = &p2;
任何指针变量都可以被初始化为NULL(注意是NULL,不是NUL,更不是null),二
级指针也不例外。也就是说把指针指向数组的零地址。联想到前面我们把尺子比作内存,
如果把内存初始化为NULL,就相当于把指针指向尺子上0 毫米处,这时候指针没有任何内
存可用。
当我们真正需要使用p 的时候,就必须把一个一级指针的地址保存到p 中,所以B)的
赋值方式也是正确的。

无法向函数传递一个数组

C 语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元
素首地址的指针
这么做是有原因的。在C 语言中,所有非数组形式的数据实参均以传值形式(对实参
做一份拷贝并传递给被调用的函数,函数不能修改作为实参的实际变量的值,而只能修改
传递给它的那份拷贝)调用
。然而,如果要拷贝整个数组,无论在空间上还是在时间上,
其开销都是非常大的。更重要的是,在绝大部分情况下,你其实并不需要整个数组的拷贝,
你只想告诉函数在那一刻对哪个特定的数组感兴趣。这样的话,为了节省时间和空间,提
高程序运行的效率,于是就有了上述的规则。同样的,函数的返回值也不能是一个数组,
而只能是指针。这里要明确的一个概念就是:函数本身是没有类型的,只有函数的返回值
才有类型。很多书都把这点弄错了,甚至出现“XXX 类型的函数”这种说法。

一级指针参数

能否把指针变量本身传递给一个函数

我们把上一节讨论的列子再改写一下:
void fun(char *p)
{
char c = p[3];//或者是char c = *(p+3);
}
int main()
{
char *p2 = “abcdefg”;
fun(p2);
return 0;
}
这个函数调用,真的把p2 本身传递到了fun 函数内部吗?
我们知道p2 是main 函数内的一个局部变量,它只在main 函数内部有效。(这里需要
澄清一个问题:main 函数内的变量不是全局变量,而是局部变量,只不过它的生命周期和
全局变量一样长而已。全局变量一定是定义在函数外部的。初学者往往弄错这点。)既然它
是局部变量,fun 函数肯定无法使用p2 的真身。那函数调用怎么办?好办:对实参做一份
拷贝并传递给被调用的函数。即对p2 做一份拷贝,假设其拷贝名为_p2。那传递到函数内
部的就是_p2 而并非p2 本身。

无法把指针变量本身传递给一个函数

fun 函数实际运行时,
用到的都是_p2 这个变量而非p2 本身。如此,我们看下面的例子:
void GetMemory(char * p, int num)
{
p = (char *)malloc(num*sizeof(char));
}
intmain()
{
char *str = NULL;
GetMemory(str,10);
strcpy(str,”hello”);
free(str);//free 并没有起作用,内存泄漏
return 0;
}
在运行strcpy(str,”hello”)语句的时候发生错误。这时候观察str 的值,发现仍然为NULL。
也就是说str 本身并没有改变,我们malloc 的内存的地址并没有赋给str,而是赋给了_str。
而这个_str 是编译器自动分配和回收的,我们根本就无法使用。所以想这样获取一块内存是
不行的。那怎么办? 两个办法:
第一:用return。
char * GetMemory(char * p, int num)
{
p = (char *)malloc(num*sizeof(char));
return p;
}
intmain()
{
char *str = NULL;
str = GetMemory(str,10);
strcpy(str,”hello”);
free(str);
return 0;
}
这个方法简单,容易理解。
第二:用二级指针。
void GetMemory(char ** p, int num)
{
*p = (char *)malloc(num*sizeof(char));
return p;
}
int main()
{
char *str = NULL;
GetMemory(&str,10);
strcpy(str,”hello”);
free(str);
return 0;
}

注意,这里的参数是&str 而非str。这样的话传递过去的是str 的地址,是一个值。在函
数内部,用钥匙(“*”)来开锁:*(&str),其值就是str。所以malloc 分配的内存地址是真正
赋值给了str 本身


函数指针

char * (*func1)(char* p1, char* p2)

是声明了一个函数指针func1,它指向一个函数,该函数的参数是两个指向char类型的指针,返回值也是一个指向char类型的指针

char* func(char* p1, char* p2)
{
int i = 0;
i = strcmp(p1,p2);
if(0 == i)
{
return p1;
}
else 
return p2;
}


main()
{
char* (*p)(char *, char *);
p = func;
(*p)("aa","bb");
return 0;
}

在vc++6.0中,给函数指针赋值时,可以用&func也可以直接用函数名func.这是因为函数名被编译之后其实就是一个地址。所以上述p=func语句等效于p=&func;

(*(void(*) ())0)();

(*(char**(*) (char **,char **))0) ( char **,char **);

试试分析以上两语句

 

http://blog.csdn.net/li4850729/article/details/7337607#

阅读(1937) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~