在这里主要讨论`int*`,`int**`,`char*`,`char**`等这些比较基本的指针类型,主要以int类型为示例。在64bit环境中地址用8B表示,int类型是4B。
关于直接寻址、间接寻址
参见相关汇编语言、计算机组成原理的书籍。关于int *
`int *p`定以了一个整型指针变量p,p本身存放的是一个int变量的地址,即在32位机器上int变量为2Bytes,p实际为4Bytes,因为地址要用32bits表示。 若有以下代码:
#include编译时会提示:`warning: ‘p’ is used uninitialized in this function [-Wuninitialized] `。 运行时显示:int main(){ int *p; printf("%p\n",p); printf("%d\n",*p); return 1; }
(nil)Segmentation fault (core dumped)这是因为声明p时候,分配的8个字节原先的内容并没有被抹去,故p指向的内容不定,甚至指向内核,这是OS不允许的。 下面这段代码就可以正常运行:
#include运行结果为:(注意内存对齐的概念)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; }
0x7fffcbaecf5c0x7fffcbaecf500x7fffcbaecf5c0内存组织如下:从内存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对于swap函数的参数`int *p1`,`int *p2`,只看p1、p2,两者是int型指针,本身代表地址。 所以调用的时候需要把地址填入里面,也就是&i、&j,注意i和j必须是int类型。 在上面的swap代码修改如下: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; }
#include输出可能如下: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运行结果可能如下: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使用指针时候应该注意防止发生下面两种情况: 第一种: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变量所占内存就不一样。
int *与一维int数组
在应用中,`int *`不只是可以用来指向一个int变量,也可以指向一个int数组——虽然本质是一样的。下面是示例:#include运行结果如下: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 32---1 2 33---1 2 34---1 2 34---1 2 35---3 2 16---0x7fff74e47a20 0x7fff74e47a24 0x7fff74e47a287---0x7fff74e47a20 0x7fff74e47a24 0x7fff74e47a280对于数组a来说,`a`代表的是数组a的首地址,`*a`则是首地址对应的元素a[0]。`a+1`代表a首地址再偏移一个int大小后的地址——即a[1]的地址,所以`*(a+1)`代表的是a[1]。为什么是偏移int大小呢,因为数组是int类型,所以这种类似`*(a+1)`的调用同样适用于float、char、struct数组。 `printf("5---%d\t%d\t%d\n",*p,*p++,*p++);`的输出是`3 2 1`是因为printf的可变参数列表是从右向左读取的。 在`printf("%d\n",a[3]);`中a[3]已经越界,但是程序依然可以运行。
int *与malloc
首先man一下malloc:zsh >> man 3 malloc | catMALLOC(3) Linux Programmer's Manual MALLOC(3)NAME malloc, free, calloc, realloc - Allocate and free dynamic memorySYNOPSIS #includemalloc函数的原型是`void *malloc(size_t size);`,void* 表示未确定类型的指针,void *可以指向任何类型的数据,在使用该函数时候应该明确指定这个新申请的空间保存什么数据类型。例如`(int*)malloc(8)`表示申请的空间保存两个int数(int为4B),更直观的写法应该是`(int *)malloc(2*sizeof(int))`。很显然,把`(double *)malloc(2*sizeof(double))`的地址提供给一个int指针是不合适的。如下: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....省略部分内容...
#include编译时候会有下面的警告:#include 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)`这样,也会引发警告。 有一点需要注意,malloc的空间是分配出来了,但是对应内存中的内容与原先不变,即这些内存空间未被初始化。
int *与NULL
NULL是一个宏定义,用来表示空指针常量。用哪个具体的地址值表示空指针取决于系统的实现。通过下面这个简单的示例可以更好的理解NULL:
#include编译时候会对`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]`。运行结果如下: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;}
p3 is NULL如果程序中需要对一些指针进行NULL判断,那么`int *p3=NULL;`这种写法是一个好习惯。
int *与二维int数组
二维数组默认是行优先存储,且存放在连续的内存中。示例如下:#include运行结果如下: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--62--6 6 6 63--0x7fff8ef28404 0x7fff8ef28404 0x7fff8ef284044--0x7fff8ef283f0 0x7fff8ef283f0 0x7fff8ef283f0 0x7fff8ef283f05--0x7fff8ef283f0 1 1 1`a`本身代表的是二维数组a的首地址,`*a`代表的是a的第一行的首地址。这也就意味着`a`虽然和`a[0][0]`地址相同,但`*a`并不是`a[0][0]`。也就是说,要获取二维数组某一元素,必须通过两次取地址,类似地适用于更高维的数组,至于原因,肯定实在代码区。 `int *`与一维维数组的差别不在为数据分配的内存上(当然,两者分配的地方不同,一个堆,一个栈),而在代码段。 不能想当然的认为`int**`与二维数组区别也不大,若要使用`int**`做一个“二维数组”还是比较麻烦的。 `int *`不能比较正规的表示表示多维数组,如下:
#include编译时候会有类型不一致的警告,运行结果如下: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));}
66
int **模拟二维数组
先看一下sizeof()一个指针是什么结果:#include运行结果如下:#include 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;}
884无论指针指向多大的内存空间,sizeof()永远是一个存放内存地址所需空间的大小。 再看一个`int**`的代码:
#include很明显,运行结果为: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;}
101010下面模拟一下二维数组:
#include运行结果如下:#include 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;}
84100,1010x2128010,0x21280180x2128030,0x2128060话说模拟完之后,真感觉应该把“模拟”二字去掉,但是这个还是和`int a[2][10]`不同。 对于`int a[2][10]`,例如取元素`a[1][2]`,获取`a[1][2]`地址的过程应该是`a首地址 + 1*10*sizeof(int) + 2*sizeof(int)`。(假定数组元素由低地址向高地址方向存放)。 对于上面代码中的自定义二维数组p,取元素`p[1][2]`过程应该是在将获取的p对应的那个地址偏移8B(即`1*sizeof(int*)`)的后得到新的地址,在新地址取得8B的数据作为新地址,跳到新地址后,便宜8B(即`2*sizeof(int)`),如此便获得元素p[1][2]。
malloc
要使得程序健壮,malloc的内存空间在不使用后一定要用free释放,这同样适用于非main函数。这其中还包含内存泄漏的问题。 如果程序是这样:#include很显然,第一次malloc申请的内存是想释放也释放不了,除非程序结束,OS回收内存。#include 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;}
int *与const
用关键字const修饰一个符号后,该符号不能被赋值,但是这并不代表这个符号变成了常量。const可以理解为read-only。
对于`const int m=10;`: const修饰m,代表之后不能再给m赋值,例如`m=0`就会报错。`const int m=10;`与`int const m=10;`同义。 对于`int i=0;const int *m=&i;`: const修饰`*m`,这也就代表着之后执行`*m=10;`是错误的。不过改变m自身的内容倒是没有什么问题,例如`int i=0,j=1;const int * p = &i;p=&j;`。`const int *m=&i`与`int const *m=&i`同义。 对于`int i=0,j=1;int * const p = &i;*p=7;`: const修饰p,代表着`p=&j`是错误的。 对于`int i=0,j=1;const int * const p=&i;`: 在这种情况下,`*p=20`和`p=&j`都是错误的。指针数组
对于`int *p[4]`,`[]`的优先级高于取值运算符`*`,所以可以看作`int *`修饰数组p的四个元素,即四个元素本身都是一个int指针。请参考下面的程序:#include对于`int (*p)[4]`: 这声明了一个指向数组的指针,数组内有4个int元素。注意`int (*p)[4]`只是作了声明,并未初始化。示例如下:#include 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;}
#include其中`p=(int (*)[4])malloc(4*sizeof(int));`对p做了初始化。如果是`p=(int *)malloc(4*sizeof(int));`则会有类型不兼容的警告。#include 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;}
int *func()、(int *)func()与int (*func)()
对于`int *func()`: 这个函数应该返回一个指针,例如:#include对于`(int *)func()`: `()`的优先级比`*`高,自己的理解是这种声明在意思上和`int *func()`相同,不过这是错误的,请遵守规则。 对于`int (*func)()`: 见#include 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;}
char *
float、double类型的指针和int完全一样,倒是char型指针有一些不一样的地方。 char指针可以这样声明:`char *p="qweasd";`,而int指针不可以这样`int *p=123;`。 另外一维的char数组认为是一个字符串,可以使用`printf("%s",p);`直接输出数组内荣,直到遇到`\0`。
写于2013-05-03。