指針
指針的聲明用*a來表示生逸,指針是保存內(nèi)存地址的一種數(shù)據(jù)類型柜砾。另外赵刑,取地址用&a來做玲躯。
指針的調(diào)用和傳值
int a = 100;
int point_t(int *a)
...
point_t(&a)
內(nèi)存
計算機可以控制嚼锄、接收電流的高(1)低(0)電位或者通(1)斷(0)電路减拭,這就產(chǎn)生了計算機能識別的二進制。
內(nèi)存的最小單位是字節(jié)(Byte区丑,1 Byte = 8 bits拧粪,一個字節(jié)是八個二進制位),一個字節(jié)可以表示00000000至11111111種意義(可以表示0~255元素)沧侥。
一個十六進制的數(shù)字可以表示4位二進制的數(shù)字(1111 = 0xf)可霎。一個字節(jié),既可以用8個二進制數(shù)字表示宴杀,也可以用2個16進制的數(shù)字表示癣朗。
32位操作系統(tǒng)
32位os的cpu地址總線是32位,支持232 個地址位旺罢,暨尋址空間是32位旷余,最多能訪問0~232次方(大約4GB空間)個內(nèi)存地址,可以理解為只能訪問并操縱最多4GB的內(nèi)存空間扁达。每個內(nèi)存地址位記錄一個唯一的內(nèi)存地址編號荣暮,每個地址編號對應存儲一個字節(jié)。
證明:
232=210 * 210 * 210 * 2 2
= 1024 byte * 1024 byte * 1024 byte * 4
= 1kb * 1024 byte * 1024 byte * 4
= 1mb * 1024 byte * 4
= 1gb * 4 = 4gb
內(nèi)存管理
內(nèi)存由os統(tǒng)一管理(編號罩驻、規(guī)劃內(nèi)存)穗酥,內(nèi)存大小的也會將多根內(nèi)存條合并后統(tǒng)一計算。os還會對多個程序進行統(tǒng)一調(diào)度惠遏、分配內(nèi)存空間砾跃,防止不同程序?qū)τ趦?nèi)存的相互侵占。
例如节吮,64位os抽高,應用程序使用前48位,暨0x7fffffffffffffff以下的內(nèi)存地址位透绩,系統(tǒng)內(nèi)核使用后邊的16位內(nèi)存地址位翘骂。
這樣把用戶的內(nèi)存和os的內(nèi)存隔離開,還有個好處就算帚豪,os的內(nèi)存不會被大量占用碳竟,避免機器卡住、卡死狸臣、死機等狀態(tài)的發(fā)生莹桅。因此,只要os可用烛亦,就可以用os將關閉應用程序诈泼,解決安全等問題懂拾。
內(nèi)存規(guī)劃
如上圖所示,os把內(nèi)存進行了分片铐达,分為系統(tǒng)內(nèi)核岖赋、棧、自由內(nèi)存區(qū)瓮孙、堆唐断、數(shù)據(jù)段、代碼段
代碼段
代碼被編譯后衷畦,會存到磁盤中栗涂,形成2進制文件。運行這個程序(例如祈争,調(diào)用main函數(shù))斤程,其實就是os把二進制數(shù)據(jù)文件(也就是計算機指令)加載到內(nèi)存代碼段中。C語言不能直接操作代碼段的地址菩混。
在代碼段忿墅,可以知道程序的二進制字節(jié)大小。例如定義如下函數(shù):
p &rect 0x4005a6
p &quadrate 0x4005dd
用quadrate 的地址高度減去rect的地址高度沮峡,就是加載到內(nèi)存中rect函數(shù)的大小
數(shù)據(jù)段
數(shù)據(jù)段存儲靜態(tài)變量疚脐、全局變量、常量邢疙,他們都會分配獨立的地址棍弄,且地址唯一
堆內(nèi)存
棧內(nèi)存
棧內(nèi)存記錄變量的地址和值,還會記錄程序已經(jīng)執(zhí)行了多少行的行號疟游,記錄程序當前狀態(tài)呼畸,調(diào)用哪個函數(shù),函數(shù)中有哪些變量颁虐,變量值蛮原。
變量名其實就是地址別名,變量其實就是內(nèi)存地址另绩,系統(tǒng)通過變量告訴cpu要到哪個地址取數(shù)據(jù)儒陨。
#include <stdio.h>
int global = 0;
int rect(int a, int b)
{
static int count = 0;
count++;
global++;
int s = a * b;
return s;
}
int quadrate(int a)
{
static int count = 0;
count++;
global++;
int s = rect(a, a);
return s;
}
int main()
{
int a = 3;
int b = 4;
int *pa = &a;
int *pb = &b;
int *pglobal = &global;
int (*pquadrate)(int a) = &quadrate;
int s = quadrate(a);
int s2 = (*pquadrate)(a);//定義一個函數(shù)指針
printf("%d\n", s);
return 0;
}
操作系統(tǒng)對于內(nèi)存的管理
代碼端
gcc編譯器對于代碼是如何優(yōu)化的
函數(shù)棧就不一樣了,一個函數(shù)可用被多次調(diào)用笋籽,一個變量也會被多次調(diào)用
gcc會優(yōu)化存儲
我們在 中看到的地址蹦漠,例如0x7fffffffddfc就是一個整型變量的首地址,然后ddfc干签、ddfd津辩、ddfe、ddff都是這個整形變量的地址容劳。(因為喘沿,一個整形變量占用四個字節(jié),四八32竭贩,一個整形是32bits)
但是 gcc會做優(yōu)化
不連續(xù)賦值蚜印,編譯器會優(yōu)化,為了讓cpu操作指令更方便更快留量,為了提升程序的執(zhí)行效率窄赋,因此,會對源代碼進行優(yōu)化楼熄。那么編譯之后的指令存儲忆绰,跟我們編寫的代碼的順序可能會不一樣。例如可岂,會先定義基本類型的错敢,然后將基本類型的地址連續(xù)放到內(nèi)存中,然后再放其他類型的缕粹,都是把同一類型的變量放到一起稚茅。這樣可用讓程序執(zhí)行更快,至于為什么平斩,下面會說亚享。另外,64位操作系統(tǒng)下绘面,指針占8個字節(jié)(32位操作系統(tǒng)欺税,指針占4個字節(jié)),因為揭璃,是64bits晚凿,一個字節(jié)存8個bits,因此塘辅,需要8個字節(jié)才能將全部的地址存儲下來晃虫。
棧這個內(nèi)存空間,保存的就是函數(shù)當前運行的狀態(tài)扣墩。包括代碼執(zhí)行到多少行指令哲银,每一個內(nèi)存空間到底是什么類型的數(shù)據(jù),什么數(shù)值呻惕。在棧中荆责,函數(shù)或者變量會被多次調(diào)用,每次調(diào)用都會分配新的獨立的棧地址亚脆。函數(shù)調(diào)用就是先進后廚的棧內(nèi)存結構做院。因此,越到棧頂?shù)暮瘮?shù)的地址越小。棧是從后向前壓棧的键耕。棧寺滚,越往后調(diào)用的地址越小。從棧頂向下分配的屈雄。
函數(shù)指針
函數(shù)指針定義后村视,也可以調(diào)用函數(shù),這種做法經(jīng)常用在我們做 回調(diào)函數(shù)來使用
一個地址酒奶,用括號包起來蚁孔,表示指向一個代碼段的函數(shù)。
帶*號表示要訪問地址對應的字節(jié)塊是啥
數(shù)組惋嚎、動態(tài)堆類型創(chuàng)建杠氢、指針運算
數(shù)組申明的內(nèi)存排列
C語言中的數(shù)據(jù)聲明,也是在棧內(nèi)存中保存的
c語言不做指針代碼的安全檢測另伍。因為鼻百,地址是連續(xù)排放的,因此质况,指針運算符就用上了
指針運算
指針偏移愕宋,的運行效率非常高,性能非常好结榄,這樣比cpu根據(jù)地址總線取地址要更方便一些
int *p=&a;
p+=3;表示將指針偏移3個int 變量占位符中贝。
在指針上做的加個減,其實做的是地址的偏移臼朗。
數(shù)組其實就是一種地址的表示
int array[3];
可用表示為邻寿,int p = array;
此時,p也就指向了array數(shù)組的第一個元素的地址视哑。
因此我們這樣寫
p[0] == array[0];
p[1] == array[1];
p[2] == array[2];
因此绣否,在c中,任何用數(shù)組操作的地方挡毅,都可以用c來代替蒜撮。因此,數(shù)組是一種指針常量跪呈。但是 段磨,指針是指針變量。因此耗绿,定義了數(shù)組苹支,數(shù)組就永遠指向某個地址
但是 ,指針指向的地址是會改變的
這就是區(qū)別误阻。
字符數(shù)組和指針字符串
字符串 指針 竟然 存在 代碼段债蜜?
注意:c語言字符串數(shù)組晴埂,以\0結尾,什么時候遇到\0也就標識著字符串結束了寻定。
malloc可以分配地址
c語言字符串是原始字符串儒洛,其實就是字節(jié)數(shù)組。