內(nèi)存訪問模式
如果你想了解更多,你應(yīng)該閱讀計算機(jī)組成和編譯原理。
Sizeof關(guān)鍵字
sizeof關(guān)鍵字由編譯器用來計算基于字節(jié)的特定類型數(shù)據(jù)的長度。例如:
sizeof=1;sizeof=4;sizeof的值是編譯時計算的,所以可以認(rèn)為是常量!
指針是什么?
我們知道,C語言中的數(shù)組是指一種類型,具體分為int型數(shù)組、double型數(shù)組、char型數(shù)組等等。
同樣,指針的概念一般是指一類數(shù)據(jù)類型,比如int指針類型、double指針類型、char指針類型等等。
一般我們用int類型存儲一些整數(shù)數(shù)據(jù),比如int num = 97,我們也用char存儲字符:char ch = 'a '。
我們還必須知道,任何程序數(shù)據(jù)加載到內(nèi)存后,內(nèi)存中都有它們的地址,也就是指針。
為了在內(nèi)存中存儲數(shù)據(jù)的地址,我們需要一個指針變量。
所以指針是程序數(shù)據(jù)在內(nèi)存中的地址,指針變量是用來存儲這些地址的變量。
以我個人的理解,指針可以理解為int integer,只不過它存儲的數(shù)據(jù)是內(nèi)存地址,不是普通數(shù)據(jù)。我們通過這個地址值訪問數(shù)據(jù),假設(shè)它是P,這意味著數(shù)據(jù)存儲位置是內(nèi)存的第P個字節(jié)。
當(dāng)然不能像int類型的數(shù)據(jù)那樣做各種加減乘除運算,這是編譯器不允許的,因為這樣的錯誤是很危險的!
圖2是指針的描述。指針的值是數(shù)據(jù)存儲地址。因此,我們說指針指向數(shù)據(jù)存儲位置。
指針長度
我們用這種方式來定義指針:
Type*p;我們說p是指向type的指針,type可以是任意類型。除了char,short,int,long等基本類型外,還可以是指針類型,比如int *,int **,或者更高級別的指針,或者結(jié)構(gòu),類,函數(shù)。所以,我們說:
Int *是指向Int類型的指針;
Int **,即 *,是指向int *類型的指針,即指向指針的指針;
Int ***,也就是 *,是指向Int **類型的指針,也就是指向指針的指針;
...我想你應(yīng)該明白
Struct xxx *是指向Struct xxx類型的指針;
其實說這么多,只是希望看到指針的時候不要被int ***,之類的東西嚇到。前面說過,指針是指向某個類型的指針。我們只看最后一個*號,前面那個正好是類型。
細(xì)心的人應(yīng)該會發(fā)現(xiàn),在“什么是指針”一節(jié)中,已經(jīng)指出指針的長度等于CPU位數(shù),大多數(shù)CPU都是32位,所以我們說指針的長度是32位,也就是4字節(jié)!注意:任何指針的長度都是4字節(jié),不管是什么指針!
所以:
Type*p;izeof的值為4,類型可以是任何類型、字符、整數(shù)、長整型、結(jié)構(gòu)、類、整數(shù)* *...
以后看到任何sizeof、sizeof、sizeof都不要在意。寫全4。只要是指針,長度就是4字節(jié)。千萬不要被類型迷惑!
為什么程序中的數(shù)據(jù)有自己的地址?
要理解這個問題,需要從操作系統(tǒng)的角度去認(rèn)識內(nèi)存。
電腦修理工眼中的內(nèi)存是這樣的:內(nèi)存在物理上是由一組DRAM芯片組成的。
換句話說,內(nèi)存是一個大的線性字節(jié)數(shù)組。每個字節(jié)都有固定的大小,由8個二進(jìn)制位組成。
最重要的是每個字節(jié)都有一個唯一的數(shù)字,從0開始,到最后一個字節(jié)結(jié)束。
如上圖,這是一個256-m的內(nèi)存,總共256x1024x1024 = 268435456字節(jié),所以它的地址范圍是0 ~268435455。
因為內(nèi)存中的每個字節(jié)都有一個唯一的數(shù)字。
所以程序中使用的變量、常數(shù)、偶數(shù)函數(shù)等數(shù)據(jù)在加載到內(nèi)存中時都有自己唯一的編號,這個編號就是這個數(shù)據(jù)的地址。
指針就是這樣形成的。
下面用代碼解釋
#include <stdio.h> intmain { char ch = 'a'; intnum = 97; printf; //ch的地址:0028FF47 printf; //num的地址:0028FF40 return0; }變量和內(nèi)存
為簡單起見,上面例子中的局部變量int num = 97用來分析內(nèi)存中變量的存儲模型。
已知num的類型是int,占用4字節(jié)內(nèi)存空,其值為97,地址為0028FF40。我們從以下幾個方面來分析。
1.內(nèi)存數(shù)據(jù)
內(nèi)存中的數(shù)據(jù)是與變量值對應(yīng)的二進(jìn)制,一切都是二進(jìn)制的。
97的二進(jìn)制是:00000000000000000000000000000110000。然而,當(dāng)使用小端模式進(jìn)行存儲時,較低的數(shù)據(jù)存儲在較低的地址,因此繪圖是相反的。
2.內(nèi)存數(shù)據(jù)的類型
內(nèi)存的數(shù)據(jù)類型決定了該數(shù)據(jù)占用的字節(jié)數(shù)以及計算機(jī)將如何解釋這些字節(jié)。
Num屬于int類型,因此它將被解釋為整數(shù)。
3.內(nèi)存數(shù)據(jù)的名稱
內(nèi)存的名稱就是變量名。內(nèi)存數(shù)據(jù)本質(zhì)上是用地址來標(biāo)識的,不存在內(nèi)存的名字這種東西。這只是高級語言提供的一種抽象機(jī)制,方便我們對內(nèi)存數(shù)據(jù)進(jìn)行操作。
而且在C語言中,并不是所有的內(nèi)存數(shù)據(jù)都有名字,比如malloc申請的堆內(nèi)存就沒有。
4.內(nèi)存數(shù)據(jù)地址
如果一個類型占用的字節(jié)數(shù)大于1,則其變量的地址就是地址值最小的字節(jié)的地址。
所以num的地址是0028FF40。存儲器的地址用于識別該存儲塊。
5.內(nèi)存數(shù)據(jù)的生命周期
Num是主函數(shù)中的局部變量,所以主函數(shù)啟動時分配給棧內(nèi)存,主執(zhí)行結(jié)束時死亡。
如果一段數(shù)據(jù)一直占用他的內(nèi)存,那么我們說它是“活的”,如果它占用的內(nèi)存被回收,數(shù)據(jù)就會“消亡”。
C語言的程序數(shù)據(jù)會根據(jù)其定義的位置、數(shù)據(jù)類型、修改的關(guān)鍵字等因素確定其生命周期特征。
本質(zhì)上,我們的程序使用的內(nèi)存在邏輯上會分為堆棧區(qū)、堆區(qū)、靜態(tài)數(shù)據(jù)區(qū)和方法區(qū)。
不同領(lǐng)域的數(shù)據(jù)有不同的生命周期。
無論未來計算機(jī)硬件如何發(fā)展,內(nèi)存容量都是有限的,所以清楚地了解程序中每個程序數(shù)據(jù)的生命周期是非常重要的。
指針操作
更多的采訪會帶這種東西:
Type*p;p++;然后問你p的值變了多少?
其實也可以認(rèn)為是在測試編譯器的基礎(chǔ)知識。所以p的值不是+1那么簡單,編譯器實際上是在p上加了sizeof。
看一段代碼的測試結(jié)果:
從觀察結(jié)果可以看出,它們的生長結(jié)果是:
2(sizeof)4(sizeof)4(sizeof)8(sizeof)4(sizeof)8(sizeof)12(sizeof)看,附加值是的大小嗎?其他結(jié)構(gòu)、類等。,不驗證你。有興趣的話,自己驗證一下。
讓我們匯編這樣一段代碼,看看編譯器是如何添加指針的:
編譯結(jié)果:
請注意注釋部分的結(jié)果,我們可以看到piv值顯示為4),然后是16)。
指針變量和指向關(guān)系
用來保存指針的變量是指針變量。
如果指針變量p1存儲變量num的地址,可以說p1指向變量num,也可以說p1指向num所在的內(nèi)存塊。這種指向關(guān)系通常由圖中的箭頭表示。
自由函數(shù)不能也不能將p設(shè)置為空。像下面這樣的代碼將有一個內(nèi)存段錯誤:
因為,第一次自由操作后,P所指向的內(nèi)存已經(jīng)釋放,但是P的值沒有改變,自由函數(shù)無法改變這個值。當(dāng)P所指向的內(nèi)存區(qū)域再次被釋放時,這個地址就變成了非法地址,會導(dǎo)致段錯誤
但是下面的代碼不會有這樣的問題:
因為p的值被編程為空,所以自由函數(shù)檢測到p為空,并將直接返回它,沒有任何錯誤。
在這里順便給大家講一個釋放內(nèi)存的小竅門,可以有效避免忘記設(shè)置指針空帶來的各種內(nèi)存問題。這個方法是自定義一個內(nèi)存釋放函數(shù),但是傳入的參數(shù)不知道指針,只知道指針的地址。在此功能中設(shè)置空,如下所示:
調(diào)用my_free后,p的值變?yōu)?,多次調(diào)用free后不會報錯!
還有一種方法也很有效,就是定義一個FREE宏,在宏中設(shè)置它空。例如
指向空,或者什么都不要指向。
錯誤指針
指針變量的值為空,或者是未知的地址值,或者是當(dāng)前應(yīng)用程序無法訪問的地址值。這樣的指針是不好的指針。
它們不能被取消指向,否則程序會有運行時錯誤,這將導(dǎo)致程序意外終止。
任何指針變量都必須確保在尋址之前指向有效且可用的內(nèi)存塊,否則會出錯。
錯誤的指針是C語言錯誤最常見的原因之一。
下面的代碼就是錯誤的示例。voidopp { int * p = NULL* p = 10//哎呀!無法處理空值}
void foo { int * p;* p = 10//哎呀!無法尋址未知地址}
void bar { int * p =1000;* p = 10//哎呀!不能將指針指向不屬于該程序內(nèi)存的地址
空*型指針
由于void的類型為空,void*的指針僅保存指針值,但會丟失類型信息。我們不知道它指向什么類型的數(shù)據(jù),只指定這個數(shù)據(jù)在內(nèi)存中的起始地址。
要想完整提取出指向的數(shù)據(jù),程序員必須對指針進(jìn)行正確的類型轉(zhuǎn)換,然后求解指針。因為,編譯器不允許void*類型的指針被直接去指。
雖然字面意思是void空,但是void指針的含義并不是空指針的含義,它指的是上面提到的NULL指針。
Void指針實際上意味著指向任何類型的指針。任何類型的指針都可以直接賦給void指針而無需強(qiáng)制轉(zhuǎn)換。
例如:
Type a, *p=&a;(Type等于char, int, struct, int*…)void*pv;pv=p;如前所述,void指針的優(yōu)點是任何指針都可以直接賦值給它,這在某些場合非常有用,所以有些操作對于任何指針都是一樣的。空指針最常用于內(nèi)存管理。最典型最知名的就是標(biāo)準(zhǔn)庫的免費功能。其原型如下:
voidfree;自由函數(shù)的參數(shù)可以是任意指針。沒人見過自由參數(shù)中的指針需要強(qiáng)到void*?
malloc,calloc,realloc函數(shù)的返回值也是一個void指針。因為內(nèi)存分配,你只需要知道分配的大小,然后返回新分配內(nèi)存的地址。指針的值是地址。不管返回什么樣的指針,結(jié)果其實都是一樣的,因為所有的指針實際上都是32位長度,它的值就是內(nèi)存的地址。指針類型僅供編譯器查看。如果malloc函數(shù)設(shè)置為下面的原型,那就完全沒有問題了。
char*malloc;實際上設(shè)置為
Type*malloc;是完全正確的,使用void指針的原因,其實前面說過,void指針就是任意指針,所以設(shè)計更嚴(yán)謹(jǐn),更符合我們的直觀理解。如果你用我前面提到的指針概念來理解童鞋,你一定會明白這一點。
結(jié)構(gòu)和指針
結(jié)構(gòu)指針有特殊的語法:->:符號
如果p是結(jié)構(gòu)指針,p->:的方法訪問結(jié)構(gòu)的成員
typedef struct{char name;intage;float score;}Student;intmain{Student stu = {"Bob ",19,98.0 };學(xué)生* ps = & amp斯圖;
ps->。年齡= 20歲;ps->。得分= 99.0;printf;返回0;}
數(shù)組和指針
1.當(dāng)數(shù)組名是正確的值時,它是第一個元素的地址。
intmain{intarr = {1,2,3};int * p _ first = arrprintf;//1 return 0;}
2.指向數(shù)組元素的指針支持遞增和遞減操作。
intmain{intarr = {1,2,3};int * p = arrfor{printf;} return0}
3.p= p+1意味著讓P指向最初指向的同一類型存儲塊的下一個相鄰存儲塊。
在同一個數(shù)組中,元素的指針可以相減,指針之間的差等于下標(biāo)之間的差。
4、p == *
p == *+ m)
5.當(dāng)使用sizeof作為數(shù)組名稱時,將返回整個數(shù)組占用的內(nèi)存字節(jié)數(shù)。當(dāng)數(shù)組名賦給指針時,sizeof運算符用于返回指針的大小。
這就是為什么當(dāng)把一個數(shù)組傳遞給一個函數(shù)時,需要用另一個參數(shù)傳遞數(shù)組元素的個數(shù)。
intmain{intarr = {1,2,3};int * p = arrprintf=%d ",sizeof);//sizeof= 12 printf= % d ",sizeof);//sizeof=4
返回0;}
函數(shù)和指針
參數(shù)和函數(shù)指針
在C語言中,參數(shù)是通過值傳遞給形式參數(shù)的,也就是說函數(shù)中的形式參數(shù)是實際參數(shù)的副本,形式參數(shù)和實際參數(shù)只是上面的相同值,而不是相同的內(nèi)存數(shù)據(jù)對象。
這意味著這種數(shù)據(jù)傳輸是單向的,即從調(diào)用者傳輸?shù)奖徽{(diào)函數(shù),但被調(diào)函數(shù)不能修改傳輸?shù)膮?shù)來達(dá)到返回的效果。
void change{a++; //在函數(shù)中改變的只是這個函數(shù)的局部變量a,而隨著函數(shù)執(zhí)行結(jié)束,a被銷毀。age還是原來的age,紋絲不動。}intmain{intage = 19;change;printf; //age = 19return0;}有時我們可以使用函數(shù)的返回值來返回數(shù)據(jù),這在簡單的情況下是可能的。
但是,如果返回值有其他用途,或者要返回多個數(shù)據(jù),則返回值無法求解。
傳遞指向變量的指針可以很容易的解決上面的問題。
void change{++; //因為傳遞的是age的地址,因此pa指向內(nèi)存數(shù)據(jù)age。當(dāng)在函數(shù)中對指針pa解地址時,//會直接去內(nèi)存中找到age這個數(shù)據(jù),然后把它增1。}intmain{intage = 19;change;printf; //age = 20return0;}讓我們舉另一個用函數(shù)交換兩個變量的值的老式例子:
#include<stdio.h>voidswap_bad;voidswap_ok;int main { inta = 5;int b = 3;swap_bad;//不能互換;swap _ ok;//okreturn 0;}
//寫voidswap_bad{ intt;t = a;a = b;b = t;}
//正確寫法:通過指針void swap _ ok { intt;t = * pa* pa = * pb* Pb = t;}
使用typedef的好處是可以用一個短的名字來表示一個類型,而不是一直使用長的代碼,這樣不僅使代碼更加簡潔易讀,還避免了代碼鍵入容易出錯的問題。強(qiáng)烈建議您使用typedef來定義復(fù)雜的結(jié)構(gòu),如結(jié)構(gòu)和指針。
每個函數(shù)本身也是一種程序數(shù)據(jù)。一個函數(shù)包含多個執(zhí)行語句。經(jīng)過編譯,本質(zhì)上是多條機(jī)器指令的集合。
程序裝入內(nèi)存后,函數(shù)的機(jī)器指令存儲在一個特定的邏輯區(qū)域:代碼區(qū)。
由于存儲在內(nèi)存中,函數(shù)也有自己的指針。
在C語言中,當(dāng)函數(shù)名是正確的值時,就是這個函數(shù)的指針。
void echo{printf;}int main{void = echo; //函數(shù)指針變量指向echo這個函數(shù)p;//通過函數(shù)的指針p調(diào)用函數(shù),相當(dāng)于echoecho;返回0;}
常量和指針
const修改誰?誰也一樣?
如果const后跟一個類型,則跳過最近的原子類型,并修飾以下數(shù)據(jù)。
如果const后面是數(shù)據(jù),直接修改數(shù)據(jù)。
intmain{inta = 1;intconst * p1 = & ampa .//const后面跟*p1,本質(zhì)上是數(shù)據(jù)a,所以修改*p1。通過p1,不能修改a的值。const int * p2 = &:a;//const后面是int類型,所以跳過int,修改*p2,效果同上
int * constp3 = NULL//const后面是數(shù)據(jù)p3。也就是說,指針p3本身是常量。
constint * constp4 = & ampa .//a的值不能通過p4來改變,p4本身就是constitution const * const P5 = &;a .//效果同上
返回0;
} typedefinet * pint _ t;//將int* type包裝為pint_t,那么pint_t現(xiàn)在就是一個完整的原子類型
intmain{
inta = 1;constpint _ tp1 = & ampa .//同樣,const跳過pint_t類型,修改p1。指針p1本身是const pint _ tconstp 2 = & amp:a;//const直接修改p,同上
返回0;
}
深度復(fù)制和輕度復(fù)制
如果兩個程序單元通過復(fù)制它們共享的數(shù)據(jù)的指針來工作,這是一個淺層復(fù)制,因為要訪問的數(shù)據(jù)沒有被復(fù)制。
如果復(fù)制訪問的數(shù)據(jù),每個單元都有自己的副本,并且對目標(biāo)數(shù)據(jù)的操作不受彼此的影響,稱為深度復(fù)制。
//測試機(jī)器是否處于小端模式。如果是,則為真;否則為假
//這種方法的依據(jù)是,C語言中一個對象的地址是這個對象占用的字節(jié)中地址值最小的字節(jié)的地址。
boolisSmallIndain{unsignedintval = 'A';unsignedchar* p = &val; //C/C++:對于多字節(jié)數(shù)據(jù),取地址是取的數(shù)據(jù)對象的第一個字節(jié)的地址,也就是數(shù)據(jù)的低地址return * p = = ' A}
1.《c語言指針詳解 還沒搞懂C語言指針?這里有最詳細(xì)的純干貨講解(附代碼)》援引自互聯(lián)網(wǎng),旨在傳遞更多網(wǎng)絡(luò)信息知識,僅代表作者本人觀點,與本網(wǎng)站無關(guān),侵刪請聯(lián)系頁腳下方聯(lián)系方式。
2.《c語言指針詳解 還沒搞懂C語言指針?這里有最詳細(xì)的純干貨講解(附代碼)》僅供讀者參考,本網(wǎng)站未對該內(nèi)容進(jìn)行證實,對其原創(chuàng)性、真實性、完整性、及時性不作任何保證。
3.文章轉(zhuǎn)載時請保留本站內(nèi)容來源地址,http://f99ss.com/jiaoyu/1688976.html