4

数组和指针,内存之间的关系

 3 years ago
source link: https://blog.csdn.net/taolaodawho/article/details/122733588
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

数组和指针,内存之间的关系

伴你永居我忆i 已于 2022-01-30 16:21:44 修改 2417
分类专栏: 指针 数组 内存 文章标签: c语言
同时被 3 个专栏收录
1 篇文章 0 订阅
1 篇文章 0 订阅
1 篇文章 0 订阅

首先论证一维数组和一级指针之前的关系,我们常常使用一级指针指针的方式访问一维数组,只有对内存的理解到位才能理解它们直接的关系。

1.数组名是数组的首地址
2.对数组名取地址得到的还是数组的首地址
3.数组的访问方式其实就是首地址+偏移的寻址访问

我们在程序中会定义很多变量,有基本类型和自定义类型
在进行开发的时候我对内存的访问访问就是通过变量名赋值的方式读写内存
但是如果你看到的直接变量的符号名你将不可能理解内存。
每一种类型都有字节宽度,
char 1字节 short 2字节 int 字节 float 4 double 8,其他的自定义类型也有一个对应的大小,对于通过变量名读写操作内存,我们需要自动在脑子里面形成一个映射关系。
int a=10; 往某段内存地址位x的内存 写入4个字节的数据10
intp =(int)20; 往某段内存地址位x1的内存 写入4个字节的数据20
int** p =(int**)30; 往某段内存地址位x2的内存 写入4个字节的数据30
int b = a; 读取某段内存地址为x的内存 读取4给字节数据值 写入首地址为b的某段内存 宽度为4,对于赋值读写需要脑子里面自动分割成一块块内存然后将变量符号名字与对于的首地址(还有宽度)对于起来,忽略数据类型的概念,对于一个变量只关注首地址+数据宽度。

对于使用指针访问数组如图下所示
基本上有点基础的都可以看出来使用直接使用数组arr和使用指针p相比少了
arr是首地址+偏移 然后直接往这个地址里面读或者写
指针的操作方式首先需要得到p对象 所占的空间里面存放的值(这里是数组首地址)然后再通过存放的值+偏移的方式 读写内存
int* p =arr; 把p这个对象 赋值数组首地址 任何指针类型32位下4个字节 64位下8个字节,p这个对象需要4个字节的空间存储arr值,因为p是一个变量,占4个字节 int* p=arr;就是 往p所占的4个字节里面存储arr值首地址
而通过p[_i]下标访问的时候 首先就需要 取出来p里面存的四个字节值y
然后y+偏移的方式 访问读或者写该指针指向数组中的内容
在这里插入图片描述
从上面的结果我们可以得到,别看到一级指针操作一维数组 和数组直接访问问之前只多了一层指针变量的寻址,但是含义就完全的变了。
如有些书上说数组作为实参会退化为指针
lea eax,[arr] 是数组首地址保存到eax寄存器中
push eax 参数入栈 此时esp寄存器的值减4,push eax相当与啥?相当于创建了一个零时对象 占4个字节的指针,传递过去,首地址值访问一块4个字节的内存后这段4个字节的内存后编译器认为这是一个指针,所以funtion里面使用sizeof可以看到占4个字节(可以私下去测试),所以把编译器给的类型去掉后 。
int a = (int)arr; 往a占的4个字节内存中写入数组首地址,从汇编的角度来看,是不是找到一丝熟悉的感觉了,没错下面这两个除了编译器附加的一些类型的限制之外没有区别,甚至可以说是完全一样的,我们完全可以使用一个int a;操作任何基本类型和抽象类型对象
int a= (int)arr;
int* p =arr;

在这里插入图片描述
函数传递一级指针和二级指针
下图可以看到传递一级是将p的地址ebp-0x3c里面存储的内容 push到栈中传参
而传递二级指针的时候是lea 得到ebp-0x3c这个值 p的地址传递进去
所以对于二级指针而言 我们很容易在funtion2函数里面改变被调用函数里面p指向的内容
(这种方式多与一些库的设计 如ffmpeg 的一些函数设计传递一个一级指针的地址进去(指向NULL)函数里面可以创建某些对象然后然后改变被调函数的指针,释放的时候同理,这样可以减少开发者所作的工作

在这里插入图片描述
可以用一级指针指针引用一位数组,以前刚学习的时候我自认而然的使用二级指针引用二维数组,发现是不可以。
进程的内存就是线性的32位可以寻址00000000-0xffffffff 4gb,从内存的角度看没有二维数组的概念,也没有多维数组的概念,对于任何一个变量,关注的应该都是首地址,宽度
int arr[4][3] ; 在内存布局上可以 int arr[12];是完全一样的没有任何区别
二维数组或者多维数组的设计知识为了理解的方便,比如使用二维数组可以更加直观的表示一个nm地图的状态,使用一维度的话没有那么的直观。三维数组可以让我们更直观的表示一个空间的概念,其实它们内存也就是一个一线性的一维数组
int arr[4][3] 我们通过 arr[_i][_j]的访问方式
mov eax,[_i];首先得到_i的值保存到eax寄存器中
lea ecx,arrtow[eax
4]; lea指令是得到[]里面的地址 arrtow首地址 + _i41(char占1个字节如果是其他类型需要对应大小)
mov edx ,[_j] ;edx寄存器保存_j的值
movsx eax.byte ptr[ecx+edx] 就是arrtow+_i
41+_j1 取出这个内存编号中的值取一个字节放入eax寄存器中 char到int有一个转换
mov [a],eax 放入a变量所在的地址中 四个字节
可以看到二维数组其实也是一个一维数组 首地址+_im宽度+j*宽度的寻址方式
三维数组同理
在这里插入图片描述
二级指针不能引用二维数组是因为是因为二级指针操作内存的方式和二维数组的完全不同,一级指针可以引用一维数组是因为它们操作内存的方式是相同的

lea eax,[arrtow]
mov dword ptr [pp],eax //这两行把char** pp = (char**)arrtow arrtow值给 pp变量
mov eax,dword ptr [_i]; //_i的值保存在eax寄存器中
mov ecx.dword ptd[pp]; //得到pp中存储的值 就是arrtow值(数组首地址)
mov edx,dword ptr [ecx+eax4] // 数组首地址+_i4的值 指针宽度占4个字节, 将arrtow+_i 4位首地址读取四个字节 到edx寄存器 (这里就寻址了一次)
mov eax.dword ptr [_j]; //读取_j的值存放在eax寄存器中
movsx ecx,byte ptr [edx+eax]//把在arrtow+_i
4地址处取的四个字节数据当做首地址 +_j再次当作首地址取一个字节 当如ecx寄存器
mov dword ptr [a],ecx, 放入a变量中

在这里插入图片描述
通过二级指针和二维数组访问内存的方式不同,可以明白为啥不能使用二级指针直接访问二维数组,同理三级指针寻址3次,四级指针4次,不管多少维数组都是和一维数组一样一次,所以如果我把断点继续指行就会应为访问未知地址挂掉。

再次将论点移到内存,我们所作的一切都只是对内存的访问,而C/C++一不小心就会出现对内存的非法的访问,所以了解内存是一件非常重要的事情,而指针只是访问操作内存的一种灵活的方式。和前面说的一样,任何指针占,所以任何一个4个字节的内存单元 一个int的大小 可以以任何的指针的方式访问内存,极度的灵活(如果对内存没有足够的理解同时就会导致极度的不安全),


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK