指针,数据存储与调试《一探内存究竟》
有关内存
文章目录
- 有关内存
- 前言
- 一、指针初阶
- 二、数据存储
-
- 1.数据类型
-
- 构造类型
- 指针类型
- 2.整形在内存中的存储
-
- 原码,反码,补码
-
- 对于整数
- 对于负数
- 2.大小端
- 3.浮点数在内存中的存储
-
- E
- E的特殊情况
-
- 1.不全为0或不全为一
- 2.全为0
- 3.全为1
-
- 补充
- 调试
- 算数转换
- 总结
前言
大体把握C语言后,要清楚C语言是一门偏底层的语言,因此就非常有必要来探究一下内存深处的样子,修炼我们的内功,对此有更深层次的理解.
一、指针初阶
指针就是变量,用来存放地址的变量。(存放在指针中的值被当做地址处理)
#include int main(){ int a = 10;//在内存中开辟一块空间 int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。 //将a的地址存放在p变量中,p就是一个之指针变量。 return 0;}
1.指针大小
对于32位机器,有32根地址线,即有2^32个地址,可以操作4g的空间,笔者在下文对此有详细的介绍
C语言整体把握
2.指针类型
既然不管何种类型指针大小都由32位与64位决定,那不同的指针类型有什么作用呢?
char *pc = NULL;int *pi = NULL;short *ps = NULL;long *pl = NULL;float *pf = NULL;double *pd = NULL;
解引用 *
*(解引用操作符)可以对地址(指针)变量 解引用通过地址对其内容做出修改
*pc = 10
#include int main(){ int n = 10; char *pc = (char*)&n; int *pi = &n; printf("%p\n", &n); printf("%p\n", pc); printf("%p\n", pc+1); printf("%p\n", pi); printf("%p\n", pi+1); return 0;}
经过调试可以发现int类型一次跳过四个字节,char类型一次跳过一个字节,即地址变量类型决定了指针移动的距离
3.野指针
位置不可只的指针,使用起来极其危险也没有意义
1.成因
1.未初始化
随意定义指针
#include int main(){ int *p;//局部变量指针未初始化,默认为随机值 *p = 20; return 0;}
2.越界
#include int main(){ int arr[10] = {0}; int *p = arr; int i = 0; for(i=0; i<=11; i++) { //当指针指向的范围超出数组arr的范围时,p就是野指针 *(p++) = i; } return 0;}
3.指针指向的空间释放
规避
初始化,不越界,释放置为空( NULL),局部变量地址慎用,检查有效性
局部地址
尽量不要在子函数中直接返回指针,由于作用域与生命周期的限定,出函数时指针所指向的空间已经被销毁.
检查有效性
if(*p != NULL)//断言,需引头文件//条件不为真时报错,对程序员十分友好assert(*p)
4.指针运算
1.±整数
结合上文,常用于指针与数组——函数等的结合
2.指针-指针
可以得到两地址之间的元素个数
关系运算时:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
可以与数组越界后一位判定目的是否成立
5.二级指针
存放地址的指针
int c= 0;int *p = &c;int **p = *p;
6.指针数组
存放指针的数组
arr数组里存放五个元素,每个元素都是int*类型int* arr[5]
有关栈帧
引图可以当做引言,但不可视为栈帧,下期详讲
二、数据存储
1.数据类型
char //字符数据类型short //短整型int //整形long //长整型long long //更长的整形float //单精度浮点数double //双精度浮点数
构造类型
- 数组
- 结构体 struct
- 枚举 enum
- 联合类型 union
指针类型
int* p;char* p;float* p;void* p;
2.整形在内存中的存储
整形分配四个字节
00000000 00000000 00000000 00000000
内存中,对整形,存储并使用的是他的二级制位的补码
原码,反码,补码
对于整数
原码,反码,补码相同,都是它所对应的二进制
对于负数
原码是其所对应二进制且最高位为1
10000000 00000000 00000000 00000000
- 二进制中最高位为符号位,0正1负
- 原码:最高位符号位为1
- 反码:符号位不变,其他位按位取反
- 补码:反码+1即为补码
原码与补码的转化方法相同
符号位不变 取反加一
2.大小端
大(小)端字节序
- 大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
- 小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中。
- 图示十六进制排序应该为00 00 00 01,01却在低地址中,即为小端
3.浮点数在内存中的存储
3.1415926
1e10
int main(){ int n = 9; float *pFloat = (float *)&n; printf("n的值为:%d\n",n); printf("*pFloat的值为:%f\n",*pFloat); *pFloat = 9.0; printf("num的值为:%d\n",n); printf("*pFloat的值为:%f\n",*pFloat); return 0;}
与思考有所不同,不得不介绍浮点数在内存中的存储规则
///
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
- (-1)^S * M * 2^E
- (-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
- M表示有效数字,大于等于1,小于2。
- 2^E表示指数位。
举例来说:
十进制的5.0,写成二进制是 101.0 ,相当于 1.01×2^2 。那么,按照上面V的格式,可以得出s=0,M=1.01,E=2。十进制的-5.0,写成二进制是 -101.0 ,相当于 -1.01×2^2 。那么,s=1,M=1.01,E=2。 - 对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。
- 对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
素材来源:比特科技B站开源素材
存储时,由于m大于1小于2,即必为1,存储时将1省略,读取时加入即可
E
科学计数法中,e可以为负数,也就有了e存入内存时其真实值必须加一个中间数,8位为127,11位为1023
例:2^10 E为10,存储时 10+127=137
10001001
E的特殊情况
1.不全为0或不全为一
正常情况,只需减去中间值127(1023)得到真实值,存储时m正常补齐即可
2.全为0
E等于1-127(或1023)即为真实值
表示一个无穷小的数
将M直接还原为0.xxxxxxx的小数
3.全为1
2的高次幂,是一个非常大的数,表示±无穷
补充
无符号的char所对应图示可以用于E的二进制展示
调试
探究底层时离不开调试,调试是一个优质程序员必须掌握的技巧。文中多处引入调试图例
调试常用
最常使用的几个快捷键:
- F5
启动调试,经常用来直接跳到下一个断点处。 - F9
创建断点和取消断点
断点的重要作用,可以在程序的任意位置设置断点。
这样就可以使得程序在想要的位置随意停止执行,继而一步步执行下去。 - F10
逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。 - F11
逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部(这是
最长用的)。 - CTRL + F5
开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用。
算数转换
long doubledoublefloatunsigned long intlong intunsigned intint
不同算数类型进行算数操作时,数据由低到高进行算数转换
注意:转换为char类型时要在二进制位低八位处进行截断,转回时要将高位补为符号位.
总结
重点在于数据存储,浮点数的存储,算数转换.