程序加載到內(nèi)存后,操作系統(tǒng)會給不同的內(nèi)存指定不同的權(quán)限,擁有讀取和執(zhí)行權(quán)限的內(nèi)存塊是代碼,擁有讀取和寫入權(quán)限的內(nèi)存塊是數(shù)據(jù)
CPU一般通過地址來取得內(nèi)存中的代碼和數(shù)據(jù)
CPU訪問內(nèi)存時需要的是地址,而不是變量名和函數(shù)名,當(dāng)源文件被編譯和鏈接成可執(zhí)行程序后,都會被替換成地址
(變量名是數(shù)據(jù),函數(shù)、字符串名、數(shù)組名是代碼塊或數(shù)據(jù)的首地址)
程序在硬盤中,要載入內(nèi)存才能運行
CPU只能從內(nèi)存中讀取數(shù)據(jù)和指令
對于CPU,內(nèi)存時存放指令和數(shù)據(jù),并不能完成計算功能
計算機:硬盤->內(nèi)存->CPU:緩存->>
CPU=運算器+寄存器+緩存寄存器:一個寄存器能夠存儲32/64位的數(shù)據(jù),CPU一般由上百個寄存器(多少位的計算機,表示的是寄存器是多少位的)。寄存器用來進(jìn)行數(shù)學(xué)運算或流程控制
緩存:內(nèi)存讀取速度小于CPU,因此將頻繁使用的數(shù)據(jù)暫時讀取到緩存區(qū)。CPU只能從緩存中讀取到部分?jǐn)?shù)據(jù),對于使用不是很頻繁的數(shù)據(jù)會直接從內(nèi)存中讀取。
指令集
虛擬內(nèi)存/地址編譯、匯編之后會變成一條條的CPU指令,
虛擬地址通過CPU的轉(zhuǎn)換才能對應(yīng)到物理地址:虛擬地址–>內(nèi)存映射機制 -->物理地址 ;
每次程序運行的時候,操作系統(tǒng)都會重新安排虛擬地址和物理地址的對應(yīng)關(guān)系,哪一段物理內(nèi)存空閑就使用哪一段
虛擬地址和物理地址的映射關(guān)系由操作系統(tǒng)決定,虛擬地址空間的大小由操作系統(tǒng)決定
虛擬地址可以使不同程序的地址空間相互隔離,防止被互相篡改
程序A 和程序B雖然都可以訪問同一個地址,但是他們對應(yīng)的物理地址是不同的,因此不會互相修改對方的內(nèi)存,因此程序不應(yīng)該直接使用物理內(nèi)存地址
虛擬地址 作為一個中間層,用來屏蔽復(fù)雜的底層細(xì)節(jié),只給用戶提供簡單的接口
內(nèi)存映射 ???
只要能夠控制這個虛擬地址到物理地址的映射過程,就可以保證程序每次運行時都可以使用相同的地址???
內(nèi)存分頁 ???
內(nèi)存空間/進(jìn)程一個進(jìn)程用于一個獨立的地址空間
一個程序可能會有多個進(jìn)程,而一個進(jìn)程對應(yīng)一個獨立的地址空間,所以一個程序可能有多個地址空間
存放操作系統(tǒng)內(nèi)核代碼和數(shù)據(jù),被所有程序共享
在程序中修改內(nèi)核空間的數(shù)據(jù)不僅會影響操作系統(tǒng)本身,還會影響其他程序(一般操作系統(tǒng)進(jìn)制用戶訪問內(nèi)核空間)
內(nèi)核空間的訪問需要操作系統(tǒng)的API函數(shù),執(zhí)行內(nèi)核提供的代碼
系統(tǒng)調(diào)用 訪問內(nèi)核空間,執(zhí)行內(nèi)核代碼(內(nèi)核也是程序)叫做 內(nèi)核模式 (發(fā)生系統(tǒng)調(diào)用時會暫停用戶程序,轉(zhuǎn)而執(zhí)行內(nèi)核代碼)
內(nèi)核用于管理硬件,提供接口供上層使用
用戶空間保存的是引用程序的代碼和數(shù)據(jù),程序私有
執(zhí)行程序叫做用戶模式
下面的內(nèi)存分區(qū)實際上說的是用戶空間中的內(nèi)存分區(qū)
內(nèi)存分區(qū) – 用戶地址空間當(dāng)運行在用戶模式的應(yīng)用程序需要輸入輸出、申請內(nèi)存等底層操作時,就需要調(diào)用系統(tǒng)API函數(shù)進(jìn)入內(nèi)核模式,執(zhí)行結(jié)束后,又回到用戶模式
數(shù)據(jù)以二進(jìn)制的形式保存在內(nèi)存中,字節(jié)是最小的可操作單位
在內(nèi)存管理中,為每個字節(jié)分配了一個編號,使用該字節(jié)時,只要知道編號就可以,這個編號,就是地址
注意:內(nèi)存分區(qū)和變量的作用域不是完全等價的,甚至是沒有關(guān)系的
獲取地址
&data
,(data可以是數(shù)值、字符)
編譯器自動分配
局部變量、形參、返回值,const
定義的局部變量也存放在棧區(qū)
操作系統(tǒng)自動管理
棧區(qū)上的內(nèi)容只在函數(shù)范圍內(nèi)存在,函數(shù)結(jié)束自動銷毀
棧區(qū)內(nèi)存地址:由高到低,即后定義的變量地址低于先定義的變量地址
先進(jìn)后出
屬于動態(tài)內(nèi)存分配
函數(shù)中定義的局部變量按照先后定義的順序一次壓入棧中,即,相鄰變量的地址之間不會存在其他變量(debug/release不同)
見函數(shù)調(diào)用棧的例子
實際上,程序啟動時會為棧區(qū)分配一塊大小適當(dāng)?shù)膬?nèi)存(包括局部函數(shù)調(diào)用棧的時候),這對于一般的函數(shù)調(diào)用就已經(jīng)足夠了,函數(shù)進(jìn)出棧只是ebp、esp等寄存器指向的變換,或者是向已有的內(nèi)存中寫入數(shù)據(jù),其實不涉及內(nèi)存的分配和釋放
只要當(dāng)函數(shù)中有較大的局部數(shù)組時,編譯器才會在函數(shù)代碼中插入針對棧的動態(tài)內(nèi)存分配函數(shù),這樣函數(shù)被調(diào)用時才分配內(nèi)存,不調(diào)用就不分配
因此棧內(nèi)存的分配效率要高于堆,也就是大部分情況下并沒有真的分配棧內(nèi)存,而是對已有內(nèi)存的操作
程序員分配內(nèi)存和釋放,若開發(fā)人員不釋放,程序結(jié)束時有系統(tǒng)回收
malloc()
free()
new
delete
堆區(qū)內(nèi)存地址:由低到高
大小由系統(tǒng)內(nèi)存/虛擬內(nèi)存上限決定
屬于動態(tài)內(nèi)存分配
注意:后申請的內(nèi)存空間并不一定在先申請的內(nèi)存空間的后面,因為先申請的內(nèi)存空間一旦釋放,后申請的內(nèi)存空間則會利用先前被釋放的內(nèi)存,從而導(dǎo)致先后分配的內(nèi)存空間在地址上不存在先后關(guān)系
堆中存儲的數(shù)據(jù)若未釋放,則生命周期等同于程序的生命周期
由于系統(tǒng)是通過鏈表來存儲空閑的內(nèi)存地址,因此堆數(shù)據(jù)時不連續(xù)的內(nèi)存空間(這個堆區(qū)域數(shù)據(jù)結(jié)構(gòu)的堆不一樣,是通過鏈表實現(xiàn)的)
對于堆分配,操作系統(tǒng)有一個記錄空閑內(nèi)存地址的鏈表,當(dāng)申請內(nèi)存時,會遍歷該鏈表,尋找第一個空間大于所申請空間的堆節(jié)點,然后將該節(jié)點從空閑節(jié)點鏈表中刪除,并將該節(jié)點的內(nèi)存空間分配給程序。另外,對于大多數(shù)系統(tǒng),會在這塊內(nèi)存空間中的首地址出記錄本次分配的大小,這樣使用delete才能正確釋放內(nèi)存空間。由于找到的堆節(jié)點的大小不一定正好等于申請的大小,系統(tǒng)會自動將多余的部分重新放入空間鏈表
free§并不能改變指針p的值,p依然指向以前的內(nèi)存,為了防止再次使用該內(nèi)存,建議將p的值手動置為NULL
free§ 只是釋放掉動態(tài)分配的內(nèi)存,p的值并不是NULL,仍然指向之前這個被釋放掉的內(nèi)存,所以if§仍然會執(zhí)行,但是輸出p指向的內(nèi)存會報錯
// c 用malloc
// 注意指針的類型轉(zhuǎn)換
char* p1=(char*)malloc(10);
free(p1);
//free(p)并不能改變指針p的值,p依然指向以前的內(nèi)存,為了防止再次使用該內(nèi)存,建議將p的值手動置為NULL
// C++ 用new
char* p2=new char[10];
delete[] p2
malloc 動態(tài)內(nèi)存分配 內(nèi)存池 – 還沒看
未初始化全局(靜態(tài))區(qū) .bass
已初始化全局(靜態(tài))區(qū) .data
在編譯期間就能確定 存儲大小的變量 的存儲區(qū),且在運行期間可以進(jìn)行修改
包括全局變量,靜態(tài)變量(包括靜態(tài)全局變量、靜態(tài)局部變量)
屬于靜態(tài)內(nèi)存分配
這塊內(nèi)存具有的讀寫權(quán)限
靜態(tài)數(shù)據(jù)區(qū)的變量只能初始化一次,也就是可修改但是不能初始化(即使二次初始化了,也會被視為無效)
.bass
讀寫
未初始化的全局變量或未初始化的靜態(tài)變量
初始化為0的全局變量或初始化為0的靜態(tài)變量
.bass段不占用可執(zhí)行文件空間,由操作系統(tǒng)初始化
.data
讀寫
已初始化的全局變量 (但初始化不為0的)
已初始化的靜態(tài)變量 (但初始化不為0的)
.data段占用可執(zhí)行文件空間,由程序初始化
.rodata
字符、字面值、字符串等常量
const 修飾的全局變量(const修飾的局部變量存放在棧區(qū))
這部分內(nèi)存只有讀取權(quán)限,沒寫入權(quán)限,即運行期間,常量區(qū)的內(nèi)容不可被修改
屬于靜態(tài)內(nèi)存分配
存放程序的代碼
.txt二進(jìn)制
只讀,不可修改
屬于靜態(tài)內(nèi)存分配
靜態(tài)/動態(tài)內(nèi)存在程序運行期間加載和卸載動態(tài)連接庫
代碼區(qū)、常量區(qū)、全局?jǐn)?shù)據(jù)區(qū)的內(nèi)存在程序啟動時就已經(jīng)分配好了,大小固定、不能由程序分配或釋放,只能等到程序運行結(jié)束由操作系統(tǒng)回收
棧區(qū)、堆區(qū)的內(nèi)存在程序運行期間可以根據(jù)實際需求來分配和釋放,不用在程序剛啟動時就備足所有內(nèi)存
char *str1="hello"; // hello字符串在常量區(qū),str1在全局?jǐn)?shù)據(jù)區(qū)
int n; // 全局?jǐn)?shù)據(jù)區(qū)
char *func()
{char *str="world"; // world字符串在常量區(qū),str在棧區(qū)
return str;
}
int main()
{int a; // 棧區(qū)
char arr[10]; // 棧區(qū)
char *pstr=func(); // 棧區(qū)
}
內(nèi)存泄漏一塊內(nèi)存沒有被指針指向它,(程序和內(nèi)存失去了聯(lián)系,再無法對他進(jìn)行任何操作),這塊內(nèi)存直到程序結(jié)束被系統(tǒng)回收
char *p=(char *)malloc(100*sizeof(char)); // 這段內(nèi)存存在內(nèi)存泄漏,無法被釋放,只有等程序運行結(jié)束由操作系統(tǒng)回收
p=(char *)malloc(50*sizeof(char));
free(p);
p=NULL;
棧
棧溢出第一種棧溢出
(整個)用于棧的內(nèi)存空間是有限的,超出大值(跟編譯器有關(guān))就會棧溢出
一個程序可以包含多個線程,每個線程都有自己的棧,棧的大是是針對線程的
第二種棧溢出
調(diào)用棧 -函數(shù)相關(guān)棧中局部變量的內(nèi)存空間有限,占用了棧中其他內(nèi)存空間的地址,導(dǎo)致棧發(fā)生錯誤
函數(shù)調(diào)用與棧有關(guān)
棧幀
又叫做活動記錄
函數(shù)調(diào)用過程中,存儲全部的信息的棧叫做棧幀
一個不典型的例子:由高到底地址分別為:實參、返回地址、xxx、一塊內(nèi)存(包括局部變量、返回值等)、xxx
函數(shù)調(diào)用慣例
調(diào)用方和被調(diào)用方之間遵守的約定
- 函數(shù)參數(shù)的傳遞方式,是通過棧傳遞還是通過寄存器傳遞
- 參數(shù)參數(shù)傳遞的方式,是從左到右入棧還是從右到左入棧
- 參數(shù)彈出方式,函數(shù)調(diào)用結(jié)束后需要將壓入棧中的參數(shù)全部彈出,使得棧在函數(shù)調(diào)用前后保持一致,(這個彈出的工作可以由調(diào)用方完成,也可以由被調(diào)用方完成)
函數(shù)調(diào)用慣例可以進(jìn)行修改 –沒看,見文檔
函數(shù)調(diào)用棧實例 – 見文檔 很重要!??!
內(nèi)存對齊局部變量分配內(nèi)存,是否初始化也與調(diào)用棧有關(guān)
https://blog.csdn.net/weixin_46251230/article/details/123755070
CPU 通過地址總線訪問內(nèi)存,一次能處理幾個字節(jié)的數(shù)據(jù),就命令地址總線讀取幾個字節(jié)的數(shù)據(jù)(32位CPU一次可以處理4個字節(jié)數(shù)據(jù),64位CPU一次可以處理8個字節(jié))
尋址步長4個字節(jié)或8個字節(jié)
將一個數(shù)據(jù)盡量放在一個步長內(nèi),避免跨步長存儲,這稱為內(nèi)存對齊
32位默認(rèn)4字節(jié)對齊,64位默認(rèn)8字節(jié)對齊
動態(tài)內(nèi)存分配 – 內(nèi)存池 --還沒看 野指針/空指針/void指針 空指針具體對齊方式,與編譯器有關(guān)
一般來講全局變量會自動內(nèi)存對齊,而局部變量不會進(jìn)行內(nèi)存對齊
對未初始化的指針賦值為NULL
空指針是不指向任何數(shù)據(jù)的指針,是無效指針
// NULL 其實是一個宏定義,指向了內(nèi)存的0地址,
#define NULL ((Void*)0)
char* s=NULL;
if (p==NULL){// ...
}
void指針
void*
表示一個有效指針,指向?qū)崒嵲谠诘臄?shù)據(jù),只是數(shù)據(jù)的類型尚未確定,在后序使用過程中需要進(jìn)行強制類型轉(zhuǎn)換
char* s=(char*)malloc(sizeof(char)*30);
野指針如果一個指針指向的內(nèi)存沒有訪問權(quán)限,或者指向一塊已經(jīng)釋放掉的內(nèi)存,那么就無法對該指針進(jìn)行操作,這樣的指針就是野指針
free
free§并不能改變指針p的值,p依然指向以前的內(nèi)存,為了防止再次使用該內(nèi)存,建議將p的值手動置為NULL
free§ 只是釋放掉動態(tài)分配的內(nèi)存,p的值并不是NULL,仍然指向之前這個被釋放掉的內(nèi)存,所以if§仍然會執(zhí)行,但是輸出p指向的內(nèi)存會報錯
避免野指針
緩存與緩沖區(qū)初始化為NULL
free之后,賦值為NULL
內(nèi)存中用于臨時保存輸入輸出數(shù)據(jù)
緩沖區(qū) :內(nèi)存空間的一部分
作用: 減少磁盤的讀寫次數(shù) ;
分類輸入緩沖區(qū)/輸出緩沖區(qū)
根據(jù)數(shù)據(jù)刷新時機全緩沖:緩沖區(qū)被填滿時(一般是對硬盤的讀寫)
行緩沖:遇到換行符時 (標(biāo)準(zhǔn)I/O),prinf(“\n”),scanf回車
無緩沖: getche(),getch() 這兩個函數(shù)沒有緩沖
(程序結(jié)束的時候 也會刷新緩沖區(qū))
(輸出之后有輸入的操作,也會刷新緩沖區(qū))注意:不同的操作系統(tǒng)對緩沖區(qū)的定義時不同的,printf在windows下無需緩沖,在linux下有緩沖
不刷新緩沖區(qū)的例子
// 這個例子,進(jìn)入死循環(huán)
// 緩沖區(qū)沒有被填滿、沒有遇到換行符、程序沒有結(jié)束,所以標(biāo)準(zhǔn)輸出始終沒有顯示字符串
int main()
{printf("hello world"); // 只要加上printf("hello world\n") 立即刷新
while (1);
return 0;
}
刷新
行緩沖,全緩沖:緩沖區(qū)滿時自動刷新
行緩沖:遇到’\n’時,自動刷新
關(guān)閉文件時自動刷新
程序關(guān)閉時自動刷新
使用刷新函數(shù)
清空輸出緩沖區(qū)
fflush(stdout) // 一般用于linux系統(tǒng)下,清空標(biāo)準(zhǔn)輸出緩沖區(qū),清空屏幕緩沖區(qū)
清空輸入緩沖區(qū)
int c
while (c=getchar()!='\n' && c!=EOF)
FILE 與緩沖區(qū)FILE *fp;
FILE 結(jié)構(gòu)體
int cn // 剩余字符,緩沖區(qū)中還有多少個字符未被讀取
char *ptr //下一個要被讀取的字符的地址
char *base // 緩沖區(qū)的基地址
int flag // 讀寫狀態(tài)標(biāo)志位
int fd // 文件描述符
FILE是文件緩沖區(qū)的結(jié)構(gòu)體,fp是指向文件緩沖區(qū)的指針
緩沖區(qū)的刷新表示將 緩沖區(qū)的指針變?yōu)榫彌_區(qū)的基地址
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧