在这里主要讨论`int*`,`int**`,`char*`,`char**`等这些比较基本的指针类型,主要以int类型为示例。在64bit环境中地址用8B表示,int类型是4B。
`int *p`定以了一个整型指针变量p,p本身存放的是一个int变量的地址,即在32位机器上int变量为2Bytes,p实际为4Bytes,因为地址要用32bits表示。
若有以下代码:
#include <stdio.h> int main() { int *p; printf("%p\n",p); printf("%d\n",*p); return 1; }编译时会提示:`warning: ‘p’ is used uninitialized in this function [-Wuninitialized] `。
(nil) Segmentation fault (core dumped)这是因为声明p时候,分配的8个字节原先的内容并没有被抹去,故p指向的内容不定,甚至指向内核,这是OS不允许的。
#include <stdio.h> int main() { int i; //默认初始化为0 int *p=&i; printf("%p\n",&i); //打印int变量i的地址 printf("%p\n",&p); //打印int指针变量p的地址 printf("%p\n",p); //打印p本身的内容 printf("%d\n",*p); //打印p本地代表的内存地址指向的值,即i的值。 return 1; }运行结果为:(注意内存对齐的概念)
0x7fffcbaecf5c 0x7fffcbaecf50 0x7fffcbaecf5c 0内存组织如下:从内存0x7fffcbaecf5c处开始4个Byte存放int变量i,从0x7fffcbaecf50开始的8个Byte开始int指针变量p的值,而这个值对应的内存单元在使用*p取值时只会取4个Byte。调用i时候类似直接寻址,调用`*p`时候类似间接寻址。因为p是`int`型指针。注意`int i;int*p=&i;`与`int i;int *p;p=&i;`是等价的,从这个角度来讲,可以把`int *`单独看作一种类型。虽然`int i;(int *)p=&i;`是错误的,但是比较容易理解(至少对我而言),例如在经典的交换数值问题上:
#include <stdio.h> void swap(int *p1,int *p2); int main() { int i=0,j=1; swap(&i,&j); printf("i=%d,j=%d\n",i,j); return 1; } void swap(int *p1,int *p2) { int temp; temp=*p2; *p2=*p1; *p1=temp; }对于swap函数的参数`int *p1`,`int *p2`,只看p1、p2,两者是int型指针,本身代表地址。 所以调用的时候需要把地址填入里面,也就是&i、&j,注意i和j必须是int类型。
#include <stdio.h> void swap(int *p1,int *p2); int main() { int i=0,j=1; printf("i addr:%p ,value:%d ; j addr :%p ,value:%d \n",&i,i,&j,j); swap(&i,&j); printf("i addr:%p ,value:%d ; j addr :%p ,value:%d \n",&i,i,&j,j); return 1; } void swap(int *p1,int *p2) { printf("p1 value:%p , p2 value:%p ; *p1 value :%d , *p2 value:%d \n",p1,p2,*p1,*p2); int temp; temp=*p2; *p2=*p1; *p1=temp; printf("p1 value:%p , p2 value:%p ; *p1 value :%d , *p2 value:%d \n",p1,p2,*p1,*p2); }输出可能如下:
i addr:0x7fff6ce4b608 ,value:0 ; j addr :0x7fff6ce4b60c ,value:1 p1 value:0x7fff6ce4b608 , p2 value:0x7fff6ce4b60c ; *p1 value :0 , *p2 value:1 p1 value:0x7fff6ce4b608 , p2 value:0x7fff6ce4b60c ; *p1 value :1 , *p2 value:0 i addr:0x7fff6ce4b608 ,value:1 ; j addr :0x7fff6ce4b60c ,value:0可以看出,在main()中调用swap()时候传递的是地址。如果是传值方式,则在swap中打印的地址肯定会不同,见下:
#include <stdio.h> void swap(int p1,int p2); int main() { int i=0,j=1; printf("i addr:%p ,value:%d ; j addr :%p ,value:%d \n",&i,i,&j,j); swap(i,j); printf("i addr:%p ,value:%d ; j addr :%p ,value:%d \n",&i,i,&j,j); return 1; } void swap(int p1,int p2) { printf("p1 addr:%p , p1 value:%d ; p2 addr :%p , p2 value:%d \n",&p1,p1,&p2,p2); int temp; temp=p2; p2=p1; p1=temp; printf("p1 addr:%p , p1 value:%d ; p2 addr :%p , p2 value:%d \n",&p1,p1,&p2,p2); }运行结果可能如下:
i addr:0x7fffef6b9f98 ,value:0 ; j addr :0x7fffef6b9f9c ,value:1 p1 addr:0x7fffef6b9f6c , p1 value:0 ; p2 addr :0x7fffef6b9f68 , p2 value:1 p1 addr:0x7fffef6b9f6c , p1 value:1 ; p2 addr :0x7fffef6b9f68 , p2 value:0 i addr:0x7fffef6b9f98 ,value:0 ; j addr :0x7fffef6b9f9c ,value:1既然这样,我们可以利用`int *`使函数“返回”多个数值。如下:
#include <stdio.h> void fun(int *p1,int *p2); int main(int argc, const char *argv[]) { int m=0,n=1; printf("m=%d\tn=%d\n",m,n); fun(&m,&n); printf("m=%d\tn=%d\n",m,n); } void fun(int *p1,int *p2) { *p1=*p1+1; *p2=*p2+1; }使用指针时候应该注意防止发生下面两种情况:
int *p; *p=20;这个可以看成错误,在gcc下编译是类似`warning: ‘p’ is used uninitialized in this function [-Wuninitialized]`这样的警告。运行程序时候会出现这样的结果:`Segmentation fault (core dumped)`。因为指针p只是被定义了,但是并没有指向具体的内存单元。
float f=1.2; int *p; p=&f;这个在编译时候也只是出现警告,运行结果为`Segmentation fault (core dumped)`。p指向的应该是int型变量,而非float——至少int和float变量所占内存就不一样。
#include <stdio.h> int main(int argc, const char *argv[]) { int a[3]={1,2,3}; int *p; p=a; printf("1---%d\t%d\t%d\n",a[0],a[1],a[2]); printf("2---%d\t%d\t%d\n",p[0],p[1],p[2]); printf("3---%d\t%d\t%d\n",*a,*(a+1),*(a+2)); printf("4---%d\t%d\t%d\n",*p,*(p+1),*(p+2)); printf("4---%d\t%d\t%d\n",*p,*(p+1),*(p+2)); printf("5---%d\t%d\t%d\n",*p,*p++,*p++); //++比*优先级高,所以相当于*(p),*(p++),*(p++) p=a; printf("6---%p\t%p\t%p\n",a,a+1,a+2); printf("7---%p\t%p\t%p\n",p,p+1,p+2); printf("%d\n",a[3]); return 0; }运行结果如下:
1---1 2 3 2---1 2 3 3---1 2 3 4---1 2 3 4---1 2 3 5---3 2 1 6---0x7fff74e47a20 0x7fff74e47a24 0x7fff74e47a28 7---0x7fff74e47a20 0x7fff74e47a24 0x7fff74e47a28 0对于数组a来说,`a`代表的是数组a的首地址,`*a`则是首地址对应的元素a[0]。`a+1`代表a首地址再偏移一个int大小后的地址——即a[1]的地址,所以`*(a+1)`代表的是a[1]。为什么是偏移int大小呢,因为数组是int类型,所以这种类似`*(a+1)`的调用同样适用于float、char、struct数组。
zsh >> man 3 malloc | cat MALLOC(3) Linux Programmer's Manual MALLOC(3) NAME malloc, free, calloc, realloc - Allocate and free dynamic memory SYNOPSIS #include <stdlib.h> void *malloc(size_t size); void free(void *ptr); void *calloc(size_t nmemb, size_t size); void *realloc(void *ptr, size_t size); DESCRIPTION The malloc() function allocates size bytes and returns a pointer to the allocated memory. The memory is not initialized. If size is 0, then malloc() returns either NULL, or a unique pointer value that can later be successfully passed to free(). ...省略部分内容... RETURN VALUE The malloc() and calloc() functions return a pointer to the allocated memory that is suitably aligned for any kind of variable. On error, these functions return NULL. NULL may also be returned by a successful call to malloc() with a size of zero, or by a successful call to cal‐ loc() with nmemb or size equal to zero. ...省略部分内容...malloc函数的原型是`void *malloc(size_t size);`,void* 表示未确定类型的指针,void *可以指向任何类型的数据,在使用该函数时候应该明确指定这个新申请的空间保存什么数据类型。例如`(int*)malloc(8)`表示申请的空间保存两个int数(int为4B),更直观的写法应该是`(int *)malloc(2*sizeof(int))`。很显然,把`(double *)malloc(2*sizeof(double))`的地址提供给一个int指针是不合适的。如下:
#include <stdio.h> #include <stdlib.h> int main(int argc, const char *argv[]) { int *p; double *p1; p=(double*)malloc(2*sizeof(double)); p[0]=1.2; p[1]=2; printf("%d\t%d\n",p[0],p[1]); printf("%ld\n",sizeof(p[0])); p1=p; printf("%ld\n",sizeof(p1[0])); printf("%lf\n",p1[0]); return 0; }编译时候会有下面的警告:
7:6: warning: assignment from incompatible pointer type [enabled by default] 12:7: warning: assignment from incompatible pointer type [enabled by default]像`(double*)malloc(1)`这样,也会引发警告。
#include <stdio.h> int main(int argc, const char *argv[]) { int i=1; int *p1=&i; int *p2; int *p3=NULL; if(&i==NULL) { printf("&i is NULL\n"); } if(p1==NULL) { printf("p1 is NULL\n"); } if(p2==NULL) { printf("p2 is NULL\n"); } if(p3==NULL) { printf("p3 is NULL\n"); } return 0; }编译时候会对`if(&i==NULL)`作出警告:`warning: the comparison will always evaluate as ‘false’ for the address of ‘i’ will never be NULL [-Waddress]`。同时也会对`if(p2==NULL)`作出警告:`warning: ‘p2’ is used uninitialized in this function [-Wuninitialized]`。运行结果如下:
p3 is NULL如果程序中需要对一些指针进行NULL判断,那么`int *p3=NULL;`这种写法是一个好习惯。
#include <stdio.h> int main(int argc, const char *argv[]) { int a[2][3]={1,2,3,4,5,6}; printf("1--%d\n",a[1][2]); printf("2--%d\t%d\t%d\t%d\n",a[1][2],*(*(a+1)+2),*(a[1]+2),*(&a[1][2])); printf("3--%p\t%p\t%p\n",&a[1][2],*(a+1)+2,a[1]+2); printf("4--%p\t%p\t%p\t%p\n",a,*a,a[0],*(a+0)); printf("5--%p\t%d\t%d\t%d\n",*a,*(*a),*(a[0]),*(*(a+0))); return 0; }运行结果如下:
1--6 2--6 6 6 6 3--0x7fff8ef28404 0x7fff8ef28404 0x7fff8ef28404 4--0x7fff8ef283f0 0x7fff8ef283f0 0x7fff8ef283f0 0x7fff8ef283f0 5--0x7fff8ef283f0 1 1 1`a`本身代表的是二维数组a的首地址,`*a`代表的是a的第一行的首地址。这也就意味着`a`虽然和`a[0][0]`地址相同,但`*a`并不是`a[0][0]`。也就是说,要获取二维数组某一元素,必须通过两次取地址,类似地适用于更高维的数组,至于原因,肯定实在代码区。
#include <stdio.h> int main(int argc, const char *argv[]) { int a[2][3]={1,2,3,4,5,6}; int i=1,j=2; int *p=a; printf("%d\n",a[i][j]); printf("%d",*(p+i*3+j)); }编译时候会有类型不一致的警告,运行结果如下:
6 6
#include <stdio.h> #include <stdlib.h> int main(int argc, const char *argv[]) { int *p=(int *)malloc(10*sizeof(int)); printf("%d\n",sizeof(int *)); printf("%d\n",sizeof(p)); printf("%d\n",sizeof(*p)); return 0; }运行结果如下:
8 8 4无论指针指向多大的内存空间,sizeof()永远是一个存放内存地址所需空间的大小。
#include <stdio.h> int main(int argc, const char *argv[]) { int m=10; int *p; int **p1; p=&m; p1=&p; printf("%d\n",m); printf("%d\n",*p); printf("%d\n",**p1); return 0; }很明显,运行结果为:
10 10 10下面模拟一下二维数组:
#include <stdio.h> #include <stdlib.h> int main(int argc, const char *argv[]) { int **p; p=(int **)malloc(2*sizeof(int *)); p[0]=(int *)malloc(10*sizeof(int)); p[1]=(int *)malloc(10*sizeof(int)); printf("%ld\n",sizeof(p[0])); printf("%ld\n",sizeof(p[0][1])); p[0][0]=100; p[0][1]=101; printf("%d,%d\n",p[0][0],p[0][1]); //内存地址 printf("%p,%p\n",&p[0],&p[1]); printf("%p,%p\n",p[0],p[1]); return 0; }运行结果如下:
8 4 100,101 0x2128010,0x2128018 0x2128030,0x2128060话说模拟完之后,真感觉应该把“模拟”二字去掉,但是这个还是和`int a[2][10]`不同。
#include <stdio.h> #include <stdlib.h> int main(int argc, const char *argv[]) { int *p; p=(int *)malloc(2*sizeof(int)); printf("%p\n",p); p=(int *)malloc(2*sizeof(int)); printf("%p\n",p); return 0; }很显然,第一次malloc申请的内存是想释放也释放不了,除非程序结束,OS回收内存。
用关键字const修饰一个符号后,该符号不能被赋值,但是这并不代表这个符号变成了常量。const可以理解为read-only。
#include <stdio.h> #include <stdlib.h> int main(int argc, const char *argv[]) { int *p[4]; p[0]=(int *)malloc(2*sizeof(int)); p[1]=(int *)malloc(2*sizeof(int)); p[2]=(int *)malloc(2*sizeof(int)); p[3]=(int *)malloc(2*sizeof(int)); p[0][0]=10; printf("%d\n",p[0][0]); return 0; }对于`int (*p)[4]`:
#include <stdio.h> #include <stdlib.h> int main(int argc, const char *argv[]) { int (*p)[4]; p=(int (*)[4])malloc(4*sizeof(int)); (*p)[0]=1; printf("%d\n",(*p)[0]); return 0; }其中`p=(int (*)[4])malloc(4*sizeof(int));`对p做了初始化。如果是`p=(int *)malloc(4*sizeof(int));`则会有类型不兼容的警告。
#include <stdio.h> #include <stdlib.h> int *func(); int main(int argc, const char *argv[]) { int *p=func(); printf("%d,%d\n",p[0],p[1]); free(p); return 0; } int *func() { int *p=(int *)malloc(2*sizeof(int)); p[0]=1;p[1]=2; return p; }
写于2013-05-03。