在我们学习C语言时,会不可避免的接触到指针,那么指针是干什么的?首先我们要引入内存的概念,正在运行的程序存储在内存中,那么我们想知道:对于一个变量/常量,到底存储在内存的那个地方,打个比方,我知道一个人住在一座公寓,却不知道他在那一层哪一个房间,这个房间号码我们称之为内存地址。
一.*和&运算符
首先我们来看看这个程序,大家看看输出结果:
int main()
{
int i = 10;
char c = 'c';
float f = 3.14;
int* pi = &i;
char *pc = &c;
float *pf = &f;
pri+ntf("the value of i is %d\n", i);
printf("the value of c is %c\n", c);
printf("the value of f is %f\n", f);
printf("\n");
printf("the address of i is %p\n", &i);
printf("the address of c is %p\n", &i);
printf("the address of f is %p\n", &i);
printf("\n");
printf("the value of pi is %p\n", pi);
printf("the value of pc is %p\n", pc);
printf("the value of pf is %p\n", pf);
printf("\n");
printf("the address of pi is %p\n", &pi);
printf("the address of pc is %p\n", &pc);
printf("the address of pf is %p\n", &pf);
printf("\n");
printf("the value of *pi is %d\n", *pi);
printf("the value of *pc is %c\n", *pc);
printf("the value of *pf is %f\n", *pf);
printf("\n");
return 0;
}
用VS2022编译的结果是:
我们暂时将int * pi = &i; 这句话理解为:有一个变量,叫做pi,它的类型是int * ,我们用 i 的地址给它赋值。其实这句话是声明了一个指向int类型的指针变量叫做pi,用 i 的地址给它赋值。
注意:多个指针的声明形式是下面第一行,而非第二行:
int* p1, * p2, * p3;
int *p1, p2, p3;//指针的声明从右向左读,例如:p1是指针,指向一个int类型;这里p2,p3都是int类型,不是指针
所以我们会看到上面的结果:pi的值与i的地址相同。
那么下面我们看到这句话:the value of *pi is 10 ,你可能感到奇怪,我们的程序似乎并没有给这个叫*的变量赋值,但是我们看到*pi的值是10,正好与i的值相等,*pc,*pf类似。
下面我们介绍& 取地址运算符 &i 显然是取 i 变量所在的内存地址
* 间接寻址运算符 *pi 是将pi存的地址所对应的值取出。即从地址0000009D4A72F9C8从低到高开始取int(32位系统是4个字节)大小的空间,将存储的值返回,也就是i 的值返回。(如果没看懂请看本文下一节)
注意:在32系统中,指针占4个字节,在32系统中,指针占8个字节。
简单理解:指针存地址,星号(间接寻址运算符)取空间,空间取多大,请看星(指针声明的位置或者使用时前面有强制类型转换)前面。
下面截取的《c和指针》一书的截图更好的说明这一点:
假设上面的程序在32位系统运行,那么d和e都是占4个字节,d里面存a的地址,e存c的地址。
指针类型
刚才我们演示的程序,都是int类型的地址赋给指向int类型的指针 ,char类型的地址赋给指向char类型的指针 ,float类型的地址赋给指向float类型的指针.那么我们不妨试试类型转换:
比如下面的例子:
int i = 10000;
int *pi1 = &i;
char* pi2 = (char*)pi1;
char* pi3 = pi2 + 1;
printf("the value of *pi1 is %x\n", *pi1);
printf("the value of *pi2 is %x\n", *pi2);
printf("the value of *pi3 is %x\n", *pi3);
可以看到,我们的pi1,pi2,pi3 所存储的地址都相同(0x000000654a4ff6d4),但最终pi1和pi2的值不同。我们看到变量i 在内存中存储的东西是:
(按字节从高到低排序)00 00 27 10
- *pi1是从0x000000654a4ff6d4开始取4个字节,结果就是00 00 27 10
- *pi2是从0x000000654a4ff6d4开始取1个字节,结果就是 10 *pi3是从0x000000654a4ff6d5开始取1个字节,结果就是27
指针 + - ,就是指针的起始地址 + - 一个指向类型大小。比如说这里char* pi3 = pi2 + 1; 就是将pi2起始地址+一个char类型的大小(一个字节)
0x000000654a4ff6d4+0x0000000000000001=0x000000654a4ff6d5(前缀0x表示数字是16进值)(后面讲到数组时还会再次演示)
简单理解:指针存地址,星号(间接寻址运算符)取空间,空间取多大,请看星(指针声明的位置或者前面有强制类型转换)前面。
再看一个程序:
int i = 1000000;
int *pi1 = &i;
char* pi2 = (char*)pi1;
char* pi3 = pi2 + 1;
char* pi4 = pi2 + 2;
char* pi5 = pi2 + 3;
char* pi6 = pi3 + 1;
long * pl=(long*)pi3;
printf("the value of *pi1 is %x\n", *pi1);
printf("the value of *pi1 is %x\n", *((char*)pi1));
printf("the value of *pi2 is %x\n", *pi2);
printf("the value of *pi3 is %x\n", *pi3);
printf("the value of *pi4 is %x\n", *pi4);
printf("the value of *pi5 is %x\n", *pi5);
printf("the value of *pi6 is %x\n", *pi6);
printf("the value of *pl is %x\n", *pl);
pi2++;
printf("the value of *pi2 is %x\n", *pi2);
以下是pi2++;之前的调试结果:
以下是pi2++;之后的调试结果:pi2++;将pi2的起始地址+一个char类型的大小。
注意到 * pl的值了吗? 0xcc000f42 ,前面一个字节 cc ,我们从没有使用过,但是我们误打误撞访问了这个未知的字节,这种情况我们要尽量避免,以免程序运行出现我们意想不到的bug。至于如何避免,在将野指针的时候再提。
参考文献:
《c和指针》(译自《pointer to c》)【美】Kenneth A. Reek著 徐波译
《彻底搞定C 指针》姚云飞
《C语言程序设计现代方法》【美】K.N.King著 吕秀峰译