给指针malloc分配空间后就等于数组吗?
⾸先回答这个的问题:严格的说不等于数组,但是可以认为它是个数组⼀样的使⽤⽽不产⽣任何问题。
不过既然这样,那它应该算是个数组吧。
所以,⼀般我们都⽤“动态数组”这种名字来称呼这种东西。
要讲清楚这个东西,涉及到malloc函数,指针类型和“[ ]”下标运算。
======分割线[0]======
malloc是C的标准库函数之⼀,⽤来分配动态内存。
⼀般来说,由C/C++编译的程序会在运⾏的时候在内存中占⽤⼀些空间,它们分为以下⼏个部分:
1.⼆进制代码区不必过多解释了,就是放⼆进制代码的地⽅。
2.常量区存放⽂字字符串和常量
3.静态存储区存放静态和全局变量
4.堆空间动态内存区,程序员可控制分配和释放的区域。
5.栈空间由编译器分配内存⽤于存储函数参数和普通变量。
malloc能操作的是程序中的堆空间,⽽普通的数组则是存放在栈空间⾥⾯的。
由于操作系统对这两部分的内存管理模式差别很⼤,所以我们⼀般认为是不同的。
堆空间是系统内存中的可⽤区域,和普遍意义上的“堆(Heap)”不同,基本上可以看作是由空闲内存组成的⼤链表。
嘛,操作系统怎么处理这东西不管了,反正你就可以认为堆空间是可⽤内存⾥的⼀⽚连续区域。
malloc函数的作⽤就是从这⼀⽚内存中划出⼀块空间来。你可以认为是malloc从内存中到了⼀⽚可以安全存放数据的可⽤空间,这样你的数据就可以放在这⽚空间⾥⾯。这⽚空间的⼤⼩是你⾃⼰指定的。通过malloc(字节数)这样简单的⽅法。
为了到这⽚空间,malloc函数会告诉你这⽚空间开头的地址,你可以把它赋值给⼀个变量存放起来。
这样我们就知道申请到的这⽚内存的⾸地址(malloc返回)和⼤⼩(程序员指定)了。
======分割线[1]======
这部分先放着,我们来看指针类型。
C语⾔的指针也有类型,但是指针总是内存地址,是⼀个(32位/64位)⼆进制整数,长度也好⼤⼩也好都是确定的,理应⼀种类型就够了。那么,指针类型的作⽤是什么呢?其实指针类型就是⽤于判断指针所指向的数据的类型。
不得不说这是⼀个⾮常天才的设计。
指针⾥存放着的是⼀个地址,它能到⼀个内存单元(复杂的东西不说了,操作系统都给你做了,你就认为是某⼀个字节就好。这个括号内部的东西写给某些较真的⼈看,实际上并不存在⼀种叫做内存单元的东西。),但是数据有长有短,数据们有些存在1个内存单元⾥⾯,有些存在多个内存单元⾥⾯。
指针是为了指向⼀个数据,那么,⽤什么⽅法可以知道这个指针想要的,到底是⼏个内存单元⾥的数据呢?
C语⾔⾥⽤了⼀种⼗分巧妙的设计——指针类型。⼀个指针指向⼀个字节地址,这个指针的类型所代表的数据结构是8个字节,那么我们就把这8个字节⾥⾯的东西都读出来,作为这个指针所指向的数据的值。
举个栗⼦:⽐如说从地址是1000开始的内存是以下的⼀⽚样⼦:
00000001 00000010 00000011 00000100 00000101 00000110 00000111 00001000
00001001 00001010 00001011 00001100 00001101 00001110 00001111 00010000
然后我有个指针a,它的值是1000。
如果这个指针是int *a。当我⽤*a去访问数据的时候,就会返回【00000001 00000010 00000011 00000100】
这些数据。
但是如果这个指针是double *a。当我⽤*a去访问数据,返回的就是【00000001 00000010 00000011 00000100 00000101 00000110 00000111 00001000】这些数据了。
不过这个指针值可是没有变化的,变化的只是指针类型⽽已。
======分割线[2]======
再回到原来那个问题,我们现在⽤malloc取得了⼀⽚空间,但是要让编译器知道其中每个数据占多少空
间,就是由指针类型来确定了。
这就是为什么malloc函数在赋值给指针之前要有⼀个强制类型转换的原因。否则void *类型⼀般应该是读不出数据的。
(此括号再次写给较真的⼈们,直接使⽤void *指针是未定义⾏为,未定义⾏为是编译器说了算的,它不想给你返回值就不给你返回值了。不过我们现在的编译器都⽐较好⼼,⼀般是会给你返回1个字节的值的,⽤起来⼤概就和char *⼀个感觉。)
⽐如说a=(int *)malloc(10240);
这⼀段代码就取得了10240个字节(10KB)的可⽤空间,然后把⾸地址告诉了变量a。然后我打算存放的数据是整型的,⼀次要求程序在这段内存⾥⾯读4个字节返回。所以我使⽤了(int *)来确定指针类型。
这样,当我们使⽤*a时,就可以访问到从a指向的地址开始的4个字节⾥⾯的数据了。
======分割线[3]======
可是我们申请了10240个字节呢。。。能存2560个整型变量呢。只能访问前4个字节有什么⽤?难道要每4个字节申请⼀次?
怎么访问后⾯的内存空间呢?
我们可以移动指针,⽐如把指针往后移4个字节。这样就能访问到这⽚区域⾥⾯的第2个整型变量了。
(注意,如果是int *类型的指针a,把a往后移4个字节的操作是a=a+1,千万不要搞成a=a+4了。为什么这么做原因后⾯再讲。)/*补充[0]*/可是还是很⿇烦,如果我要⼀次⼀次的遍历这⽚区域,或者同时访问⾥⾯的第12个和第450个变量。那么程序⾥就必然存在2个或2个以上的指针。
为了简化访问⽅法,C语⾔使⽤了⼀种简单的对指针运算——[ ]下标运算。
[ ]运算符是C语⾔⼏乎最⾼优先级的运算符。[ ]运算符需要两个操作数,⼀个指针类型,⼀个整数。/*补充[1]*/
标准的写法是这样的:a[int]。这样编译器会返回 *(a+int) 的值。
这样做的话相当于⼀个⼗分好⽤的临时指针的移动。
如果我要访问第12个变量只需要写a[11]就好了。编译器会理解这个运算的规则,⾃动的把a指针进⾏⼀次以下的操作:
int *temp;temp=a+11;return *temp;
嗯,⼤概就是这个样⼦。
======分割线[4]======
该回到正题了。因为C语⾔为我们提供了这样的⽅法,使得我们申请到的⼀⽚内存连续区域,可以使⽤这样的⽅法,像数组⼀样的访问到。不过数组明显更加简单。int a[2560]同样是申请⼀⽚10KB的空间,这部分空间存放在栈空间⾥⾯。内存地址也是完全连续的。
值得注意的是,数组名a其实被声明为常量指针const int *,它同样存储的是数组的⾸地址。
(本括号写给较真的⼈看,C/C++⾃动把数组类型隐式声明为常量指针,这个动作其实更类似于隐式转换,⽽不是直接声明那个指针。)然后这么说来[ ]。操作符在普通数组上和⽤malloc⽣成的动态数组上的操作是完全⼀样的,都是类似于*(a+i)的操作。
所以从这层意义上来讲,⽤malloc分配的空间本质上和数组没什么区别。它们主要的区别还是存放的内存区域在操作系统对内存管理上的区别。
不过这层区别也不⼩,所以⼀般不把malloc分配的空间等同为数组,⽽是⽤“动态数组”来区别的对待它。
怎么给数组赋值最重要的区别也许就是使⽤完了以后记着⽤free释放掉。
======分割线[5]=完,下⾯是补充内容=====
补充[0]:操作系统给你分配的内存,⼀般只有栈空间是连续的。⽐如你申请⼀个10KB的堆空间区域,其实很少能申请到全连续的⼀段内存。⼀般都是中间会有断开的⽅式。
操作系统是⽤类似于链表的⽅式来管理这些分⽚的内存空间的。
所以说,虽然指针本质就是个整数,但是指针的运算不是简单的改变这个整数,⽽是指向下⼀存储区这样的意思。
因为如果是让你简单的改变这个整数,很可能这个指针指向的就是内存中其他程序的区域了。甚⾄是系统重要的代码区域。这是绝对不允许的,所以编译器才会采⽤这样的定义。即给int *a定义的指针a进⾏a++这种运算的过程实际上相当于:1.返回a的当前值 2.到a当前的内存区域 3.在链表中查下4个字节的存放区域,并把⾸地址赋值给a。
补充[1]:事实上ANSI C并没有定义两个操作数的顺序。指针[整数]只是⼀种常⽤写法。写成整数[指针]也未尝不可。
定义数组int a[20]之后,使⽤5[a]⼀样可以访问到这个数组⾥第6个整型变量的值。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论