指针本质论
1.指针是什么? 和一般变量有什么区别?
指针就是地址,和一般变量没有本质区别,仅仅是它有自己的规则。
int a=100;
int *p=&a;
printf("%d\n",a); // 100
printf("%p\n",p); //0xbfa47858
a是一个变量名,类型是int,值是100,a有自己的地址&a
p是一个变量名,类型是int*,值是0xbfa47858,p有自己的地址&p
我们发现指针和普通变量没什么区别,那到底为什么我们觉得它难呢?
主要是指针是一种新的变量类型,它有自己的一套运算规则。
2.指针的运算规则
#1.指针的类型
定义不同的指针,它有自己的类型. 如,
int *p -> int 型指针
char *p -> char 型指针
int (*p)[2] -> int型的数组指针
void (*p)() -> 函数指针
...
果然很多,怪不得那么难.
#2.指针的强制类型转换
int *p;
char *q=(char*)p;
这是把int型指针转换成char型指针,和变量的强制类型转换一样,so easy...
#3.指针的加减规则
指针有自己一套加减规则。
int a=100;
int *p=&a;
printf("%d\n",a); // 100
printf("%p\n",p); //0xbfa47858
p=p+1; p是int型指针,sizeof(int)=4,所以p+1其实加的是一个int型变量的大小,所以p=0xbfa4785c
char *p;
p=0x77777777; //p是一个变量,可以给p直接赋值
p=p+1; p是char型指针,sizeof(char)=1,所以给p+1其实加的是一个char型变量的大小,所以p=0x77777778
....
我们发现,还是so easy...
#4. 指针的解引用,也就是*p
解引用是有步骤的
如 type *p; 求*p //type是类型
1.找到指针所存放的内存单元
2.从这个地址开始读取sizeof(type)个字节大小
3.把读取的内容按type类型解释,解释出来的就是*p
eg.
short a=256;
short *p=&a;
printf("%d\n",*p); //256,so easy...
1.p所对应的就是a的地址
2.a->00000001 00000000,读取2个字节,所以读取到的就是00000001 00000000
3.把00000001 00000000解释成为short类型,明显是256
char *q=(char*)p;
printf("%d\n",*q); //0, maybe not so easy....
1.p所对应的就是a的地址
2.a->00000001 00000000,读取1个字节(sizeof(char)),所以读取到的就是00000000(小段字节序)
3.把00000000解释成为char类型,明显是0
如果上面的都懂了,那么下面的就不是难事
我要读取a的高地址,怎么写?
char *q=(char*)p+1; //(char*)p指向a的地地址,+1,因为是char型指针,所以加一个字节,指向a的高地址
printf("%d\n",*q); // 读取sizeof(char)个字节,即00000001,所以是输出1
short a=256;
char *p=&a;
printf("%d\n",*(short*)p);
p指向a的低地址,将p强制转换为short型制作,所以读取sizeof(short)个字节,所以输出256
short a=256;
char *p=&a;
printf("%d\n",*((char*)(short*)p+1)); //输出结果为1,so easy...
再来点有挑战的吧!
short a=256;
char *p=&a;
printf("%d\n",*(short*)(*(int*)&p)); //256
如果这道题目做对了,那么指针也就被你俘虏了。
关键就是这是为什么是int*,主要是指针所占空间是4个字节(32位主机)
#5.那么数组指针和指针数组到底怎么回事?
数组指针是指针,指针数组是数组,就这区别
数组指针只存在与多维数组吗?,下面将带给你不一样的认识
我们知道数组名所代表的就是数组首地址,那么对数组名取地址得到的是什么?
一维数组:
int a[2]={1,2};
(gdb) p a
$5 = {1, 2}
(gdb) p &a[0]
$6 = (int *) 0xbffff0c0 //&a[0]是普通int型指针
(gdb) p &a[0]+1
$7 = (int *) 0xbffff0c4 //所以+1加的是sizeof(int)
(gdb) p &a
$8 = (int (*)[2]) 0xbffff0c0 //一维数组名的地址其实是个数组指针,这个就有点像sizeof(a)返回的是整个数组大小
(gdb) p &a+1
$9 = (int (*)[2]) 0xbffff0c8 //所以+1加的是2*sizeof(int)
(gdb) p a+1
$10 = (int *) 0xbffff0c4 //我们发现当数组名用于表达式时自动转换为数组首地址
二维数组:
int a[2][2]={1,2,3,4};
(gdb) p a
$1 = {{1, 2}, {3, 4}}
(gdb) p &a[0]
$2 = (int (*)[2]) 0xbffff0b8 //因为是二维数组,所以&a[0]是数组指针
(gdb) p &a[0]+1
$3 = (int (*)[2]) 0xbffff0c0 //所以+1,加的是2*sizeof(int)
(gdb) p &a
$4 = (int (*)[2][2]) 0xbffff0b8 //对数组名取地址代表的是整个数组的首地址,有点整体的意思
(gdb) p &a+1
$5 = (int (*)[2][2]) 0xbffff0c8 //所以+1加的是sizeof(a)
(gdb) p a==&a
$7 = 1
(gdb) p a==&a[0]
$8 = 1
(gdb) p a==&a[0][0]
$9 = 1
其实从值的角度来说,a,&a,&a[0],&a[0][0]都是数组首地址的值
int a[2][2]={1,2,3,4};
int (*p)[2]=a;
如果我要输出3:
printf("%d\n",**(p+1)); //3
这个p已经是指针了,为啥要**呢,难道这是指针的指针?
在一维数组中我们知道: *(a+i)=a[i],在二维数组中同样成立.
*(p+1)=a[1],只不过此时的a[1]代表的是第二行的首地址,所以会出现**的问题.
printf("%d\n",*(int*)(p+1)); // 3,这样写我想更清楚,易懂
我们来点难点的吧?
short a[2][2]={256,256,256,256};
short *q=a[1];
printf("%d\n",**(char(*)[2])q); // 0,你对了吗?
q指向的是a[1]首地址,即256,然后将256当作2列char型数组,00000001 00000000,
所以最后输出来的是低地址的值->0
再来一个更变态的吧!!
char a[2][4]={8,7,6,5,4,3,2,1};
short *q=a;
printf("%d\n",*(*(((short(*)[2])q)+1)+1)); //小段字节序算
答案是258,你做对了吗?
解释:
(short(*)[2])q -> 将q转换为short型的数组指针
*(((short(*)[2])q)+1) -> 此时指向的是4
*(*(((short(*)[2])q)+1)+1) -> 此时指向的是2,00000010 00000001,而00000010是低地址,
所以最后为00000001 00000010 -> 258
分享到:
相关推荐
C#本质论(中文版) 继承 数组 集合 反射 内存管理和指针
C#5.0本质论第四版,高清扫描的,对C#5.0技术讲的比较详细,第1章 C#概述 1 1.1 Hello,World 1 1.2 C#语法基础 3 1.2.1 C#关键字 3 1.2.2 标识符 4 1.2.3 类型定义 5 1.2.4 Main 6 1.2.5...
使用手势控制鼠标指针 抽象的 这个项目是一个鼠标模拟系统,它执行由鼠标执行的与您的手部动作和手势相对应的所有功能。 简而言之,摄像机会捕获视频,并根据您的手势,您可以移动光标并执行左键单击,右键单击,...
引起缓冲区溢出问题的根本原因是 C(与其后代 C++)本质就是不安全的,没有边界来检查数组和指针的引用,也就是开发人员必须检查 边界(而这一行为往往会被忽视),否则会冒遇到问题的风险。标准 C 库中还存在许多...
8.32 设计方法论 196 8.33 真正的interface 198 8.34 真正的对接口进行编程 200 8.35 实践方法之极限编程 200 8.36 设计模式复用与框架复用 201 第三部分 进阶: C,C++代码阅读与控制 201 第9章 语法与初级标准库 ...
浏览器和Java类库一起从本质上决定了Java应该用来写哪种类型的应用程序,而高速的游戏和图象则不在其中。这给Java带来了不好的影响,因为人们注意的不是语言,而是它的运行环境。现在,浏览器已经不能控制一切了,...
引起缓冲区溢出问题的根本原因是 C(与其后代 C++)本质就是不安全的,没有边界来检查数组和指针的引用,也就是开发人员必须检查 边界(而这一行为往往会被忽视),否则会冒遇到问题的风险。标准 C 库中还存在许多...
动态无状态计算机 ...一种用经典位的运算来描述,另一种用量子态的演化来描述,因此通用图灵机以及所有经典计算机可能无法模拟某些行为以相反,从物理上讲,有可能实现一种与经典计算机科学本质上不同的新
以实现对齐如果结构像平时那样直接声明为结构时,E会自动把结构转换为C/C++中的标准的那种,但结构写为通用型时E只简单传递结构指针不做转换R_指针到数据_变量()【本质是将源数据的值写入目标变量,源数据不是基本...
其中红色部分巧妙的利用指向指针的指针为指针puc_card_config_tab赋值,而在兰色部分使用该指针。但在Get_Config_Table函数中有可能失败返回而不给该指针赋值。因此,以后使用的可能是一个非法指针。 指针的使用是...
其中可以选择的组件对象类型很多,但本质上,就是让向导帮忙加上一些 默认接口。 3. 增加自定义类 CMath(接口 IMath),见图 5。 图 5 填写类名 4. 填写属性接口,见图 6。 图 6 选择属性 4 5. 添加接口成员函数 图 7 ...
开发的本质。 本书是作者结合教学和科研实践经验编写而成的,不仅详细介绍了Windows内核原理,而且介绍了编程技 巧和应用实例,兼顾了在校研究生和工程技术人员的实际需求,对教学、生产和科研有现实的指导意义 ,...
归纳了多种调试驱动程序的高级技巧,如用WinDBG和VMWARE软件对驱动进行源码级调试,深入Windows操作系统的底层和内核,透析Windows驱动开发的本质。 本书是作者结合教学和科研实践经验编写而成的,不仅详细介绍了...
9.5.3 再论动态内存分配 269 9.6 template 272 9.6.1 封装C++的成员函数调用 272 9.6.2 常数数量的对象复制 274 9.6.3 对象计数 275 9.6.4 避免重复代码 277 9.6.5 选择最佳的容器 279 9.6.6 延迟运算 281 9.6.7 ...
9.5.3 再论动态内存分配 269 9.6 template 272 9.6.1 封装C++的成员函数调用 272 9.6.2 常数数量的对象复制 274 9.6.3 对象计数 275 9.6.4 避免重复代码 277 9.6.5 选择最佳的容器 279 9.6.6 延迟运算 281 9.6.7 ...
1.9再论shared_ptr 的线程安全.. . . . . . . . . . . . . . . . . . . . . . . . 17 1.10shared_ptr 技术与陷阱. . . .. . . . . . . . . . . . . . . . . . . . . . . . 19 1.11对象池. . . . . . . . . . . . . ....