給指針malloc分配空間后就等於數組嗎?


轉自http://www.guokr.com/blog/480156/

這是對http://www.guokr.com/post/502827/帖子內容的回答。@ET 
由於一時興奮寫的過長,期間果殼抽來抽去,最后還導致排版徹底失敗。。於是還是決定寫成一篇日志。


首先回答這個的問題:嚴格的說不等於數組,但是可以認為它是個數組一樣的使用而不產生任何問題。
不過既然這樣,那它應該算是個數組吧。
所以,一般我們都用“動態數組”這種名字來稱呼這種東西。

要講清楚這個東西,涉及到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個整型變量的值。

======分割線[6]=參考資料=====
http://blog.csdn.net/hzhzh007/article/details/6424638
http://lionwq.spaces.eepw.com.cn/articles/article/item/18555
http://blog.163.com/hangqiang321@yeah/blog/static/164202800201181453113213/
http://hi.baidu.com/yang_qi168/item/5be27010f1b8421eb88a1a46
http://baike.baidu.com/link?url=w21P6YmZq0LhSqPudbRrOtizrYynV1JzAP_jSNn-fFYzhDmBEQavQgIlXJ7o5nxm



注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com