内存那些事儿
目錄
一、C++內存模型?
1.什么是內存
2.C++內存模型
(1)棧
(2)unused
(3)堆
(4)數據段
(5)代碼段(text)
二、各語言內存模型
1.堆與棧的本質是什么
2.各語言內存模型
三、進程與內存
1.指針與引用
2.進程獨占一個連續的4G大小的內存
四、函數運行時在內存中是什么樣子???
1.函數運行時棧 (run time stack)
2.棧幀(stack frames、call frames)
(1)棧幀
(2)控制轉移
(3)傳遞參數與獲取返回值
(4)局部變量
五、申請內存時底層發生了什么??
1.內核態&用戶態
(1)內核區
(2)用戶區
2.標準庫
3.malloc內存分配
六、自己實現malloc內存分配器
1.為什么要動態申請內存?
2.內存分配大致步
(1)跟蹤內存分配狀態
(2)怎樣選擇空閑內存塊
(3)內存分配
(4)內存釋放
七、內存池技術是如何實現的??
1.內存池技術
2.內存池技術原理
3.線程局部存儲+內存池
4.內存池形式
?八、C++內存管理全景指南
1.線程與數據競爭
2.?C++對象內存模型
(1)空類
(2)非空類
(3)非空虛基類
3.常見解決內存問題的方法
(1)靜態代碼檢測
(2)動態代碼檢測
4.內存池優勢
九、C++成員函數在內存中的存儲方式
十、C++ 虛函數表解析
1.虛函數表
2.虛函數&析構函數
3.虛函數&繼承
(1)一般繼承(沒有overwrite)
(2)一般繼承(有虛函數覆蓋)
(3)多重繼承(無虛函數覆蓋)
(4)多重繼承(有虛函數覆蓋)
十一、為什么SSD不能當做內存用??
1.內存條和固態硬盤的區別
(1)內存條
(2)固態硬盤(SSD)
2.CPU是怎么訪問文件內容的呢?
3.SSD壽命短
4.總結
十二、10 個內存引發的大坑,你能躲開幾個???
1.malloc后要初始化
2.malloc后要free
一、C++內存模型?
1.什么是內存
????????內存的基本單位:字節。一個字節,8位:00000000
????????當計算機在執行我們的程序時,無論是我們的機器指令還是機器指令操作的數據,都需要存放在這些小盒子中(內存)。
2.C++內存模型
????????C/C++程序在存儲時有數據區和代碼區。
????????C/C++程序在被執行時,需要在內存中劃出兩段區域用于存放數據,這兩個區域就是我們熟悉的堆(Heap)和棧(Stack),也稱堆區和棧區,其中堆區緊鄰數據段,在數據段之上,而棧在最上方,棧和堆之間是尚未被使用的內存,隨著程序的運行,當程序申請內存時棧區和堆區之間的空隙會減小,當程序釋放內存后空隙會擴大,這就是C/C++程序的內存模型。
?
參考文章:
C++內存模型_五月525的博客-CSDN博客
C++內存模型_五里的博客-CSDN博客_c++內存模型
讓c/c++堆棧的工作機制變得通俗易懂?
(1)棧
????????進行函數調用,保存參數、返回值及局部變量。
棧幀從高地址到低地址的方向依次是:
?
(2)unused
(3)堆
????????通過malloc或new動態分配的內存。
(4)數據段
????????在C中區分為bss段和data段(C++不區分):
- bss段是指那些沒有初始化的和初始化為0的全局變量。bss類型的全局變量只占運行時的內存空間,而不占文件空間。
- data段:初始化(非0)的全局變量和靜態變量。data類型的全局變量是即占文件空間,又占用運行時內存空間的。
(5)代碼段(text)
????????用來存放機器指令的一塊內存區域。這部分區域的大小在程序運行前就已經確定,并且內存區域通常屬于只讀,某些架構也允許代碼段為可寫,即允許修改程序。在代碼段中,也有可能包含一些只讀的常數變量,例如字符串常量等。
????????text和data段都在可執行文件中,由系統從可執行文件中加載;而bss段不在可執行文件中,由系統初始化。
????????不管數組在棧區還是堆區,數組的生長方向都是從下往上的。也就是從低地址到高地址的。
????????棧的生長方向是由上往下(從高地址往低地址)。棧頂和棧底不是上下決定,而是由入棧方向決定!
????????堆的生長方向從下往上(從低地址到高地址)。
二、各語言內存模型
1.堆與棧的本質是什么
????????在編程語言中,堆區和棧區本質上都是內存,因此二者在本質上沒有任何區別,只不過這兩塊內存的使用方式是不一樣的。為什么要區分呢?根源在于:內存是有限的。
????????在數據結構與算法中,我們也有堆和棧的概念,但那里指的不是內存,而是兩種數據結構。
2.各語言內存模型
????????C/C++分配內存是直接在物理內存中進行的,而Java、Python等程序是將內存分配請求交給解釋器,解釋器再去物理內存上進行分配。
????????Java、Python等程序的一大優點就是內存的自動化管理,而C/C++程序員需要自己來管理從堆上分配的內存。內存管理這一項工作在Java、Python等程序中被解釋器接管了,解釋器的這項功能被稱為“垃圾回收器”。
三、進程與內存
1.指針與引用
????????只有C/C++這樣的編譯型語言才會有“指針”這樣一個概念,指的是當前的對象放在了內存中的哪個位置上了。在比如Java、Python等語言中只有“引用”這樣一個概念。
2.進程獨占一個連續的4G大小的內存
????????每個進程獨占一個連續的4G大小的內存,從內存地址0開始,一直到0xffffffff,其中最上方的1G留給了操作系統使用,下方的3G是留給進程自己使用的,其中程序員可以操作的區域就是圖中的堆區和棧區。
?
?????????每個進程都認為真實的內存就是4G,其中1G被操作系統使用,剩余部分被進程使用,也就是可以被程序員使用。注意這是不受真實物理內存限制的,也就是說,即使真實的物理只有256MB,進程同樣認為在內存是4G,其中1G是操作系統的,剩余3G是進程自己獨占的,程序員依然可以按照內存大小是3G來寫程序。所以在大小256MB的真實物理內存上,程序員依然可以一次性申請超過256MB的內存而且可以申請成功,后續內存的使用也不受影響。
????????在虛擬內存上程序員分配內存不受真實物理內存大小的限制。
四、函數運行時在內存中是什么樣子???
1.函數運行時棧 (run time stack)
????????進程和線程的運行體現在函數執行上,函數的執行除了函數內部執行的順序執行還有子函數調用的控制轉移以及子函數執行完畢的返回。其中函數內部的順序執行乏善可陳,重點是函數的調用。
????????函數調用的活動軌跡:是一個First In Last Out 的順序,天然適用于棧這種數據結構來處理。即:使用棧這種結構就可以用來保存函數調用信息。
2.棧幀(stack frames、call frames)
(1)棧幀
????????保存函數運行時的各種信息。每個未完成運行的函數占用一個獨立連續區域(包含這個函數涉及的參數,局部變量,返回地址等相關信息),稱為棧幀。這些棧幀構成了我們通常所說的棧區。
????????在計算機中,每個函數棧幀的“底部”和“頂部”的信息——也就是內存地址,分別存放在兩個寄存器中:BasePointer(BP)寄存器以及StackPointer(SP)寄存器,即我們熟悉的rbp以及rsp,32位下為ebp以及esp。
????????當調用函數時,就要壓入一個新的棧幀,發起調用函數的棧幀成為調用者棧幀,被調用函數的棧幀則稱為當前棧幀(rsp 和 rbp 之間的內存空間);被調用的函數運行結束后回收棧幀,回到調用者棧幀。這一過程都是自動的,由系統分配與銷毀,無需手動調度。
(2)控制轉移
????????A調用B:CPU從開始執行屬于函數A的指令切換到執行屬于函數B的指令,我們就說控制從函數A轉移到了函數B。
?
????????call指令除了給出跳轉地址之外還有這樣一個作用,也就是把call指令的下一條指令的地址,也就是0x40056a push到函數A的棧幀中。
call?? 首先將被調函數的參數入棧,最后是返回地址入棧,再跳到被調函數起始地址
leave? 準備返回時的楨棧 : 令棧指針先指向當前楨的起始處(這里保存的是調用者楨的起始地),出棧(楨指針重置為調用者楨的起始;且棧指針指向返回地址)
等同于 :? ?
????????movl %ebp,%esp?
????????popl %ebp
ret??? (棧指針指向返回地址)出棧并跳到那個位置(返回地址)。
(3)傳遞參數與獲取返回值
????????在x86-64中,多數情況下參數的傳遞與獲取返回值是通過寄存器來實現的。寄存器的數量是有限的,當參數個數多于寄存器數量時剩下的參數直接放到棧幀中,這樣被調函數就可以從前一個函數的棧幀中獲取到參數了。
?
?????????調用函數B時有部分參數放到了函數A的棧幀中,同時函數A棧幀的頂部依然保存的是返回地址。
通常情況,棧都是從棧底往棧頂壓:
?
(4)局部變量
????????函數內部定義的變量被稱為局部變量,這些變量同樣可以放在寄存器中,但是當局部變量的數量超過寄存器的時候這些變量就必須放到棧幀中了。
?
?????????在向寄存器中寫入局部變量之前,一定要先將寄存器中開始的值保存起來,當寄存器使用完畢后再恢復原值就可以了。那么我們要將寄存器中的原始值保存在哪里呢?沒錯,依然是函數的棧幀中。
?
?注意
????????棧區是有大小限制的,當超過限制后就會出現著名的棧溢出問題,顯然上述代碼會導致這一問題的出現。因此:
五、申請內存時底層發生了什么??
?
?????????x86 CPU提供了“四界”:0,1,2,3,這幾個數字其實就是指CPU的幾種工作狀態,數字越小表示CPU的特權越大,0號狀態下CPU特權最大,可以執行任何指令,數字越大表示CPU特權越小,3號狀態下CPU特權最小,不能執行一些特權指令。
????????一般情況下系統只使用0和3,因此確切的說是“兩界”,這兩界指的是“用戶態(3)”以及“內核態(0)”,接下來我們看看什么是內核態、什么是用戶態。
1.內核態&用戶態
(1)內核區
????????CPU執行操作系統代碼時就處于內核態,在內核態下CPU可以執行任何機器指令、訪問所有地址空間、不受限制的訪問任何硬件。
(2)用戶區
????????在用戶態我們的代碼處處受限,不能直接訪問硬件、不能訪問特定地址空間,否則操作系統直接將你kill掉,這就是著名的Segmentation fault、不能執行特權指令。
????????操作系統為普通程序員留了一些特定的暗號,這些暗號就和普通函數一樣,程序員通過調用這些暗號就能向操作系統請求服務了,這些像普通函數一樣的暗號就被稱為系統調用,System Call,通過系統調用我們可以讓操作系統代替我們完成一些事情,像打開文件、網絡通信等。
2.標準庫
????????標準庫對程序員屏蔽底層差異,這樣程序員寫的程序就無需修改的在不同操作系統上運行了。從分層的角度看,我們的程序一般都是這樣的漢堡包類型:
?
?????????最上層是應用程序,應用程序一般只和標準庫打交道(當然,我們也可以繞過標準庫),標準庫通過系統調用和操作系統交互,操作系統管理底層硬件。這就是為什么在C語言下同樣的open函數既能在Linux下打開文件也能在Windows下打開文件的原因。
3.malloc內存分配
????????我們分配內存時使用的malloc函數其實不是實現在操作系統里的,而是在標準庫中實現的。
????????平時在C語言中使用malloc只是內存分配器的一種,實際上有很多內存分配器,像tcmalloc,jemalloc等等,它們都有各自適用的場景,對于高性能程序來說使用滿足特定要求的內存分配器是至關重要的。
????????如果內存分配器中的空閑內存塊不夠用了該怎么辦呢?malloc內存不足時要向操作系統申請內存,操作系統才是真大佬,malloc不過是小弟,對每個進程,操作系統(類Unix系統)都維護了一個叫做brk的變量,brk發音break,這個brk指向了堆區的頂部。brk()系統調用就是用來增加或者減小堆區的。
?
現在就可以簡單總結一下了,當我們申請內存時,經歷這樣幾個步驟:
?
?????????此時操作系統根本就沒有真正的分配物理內存,程序員從malloc拿到的內存目前還只是一張空頭支票。
????????當我們真正使用這段內存時,這時會產生一個缺頁錯誤,操作系統捕捉到該錯誤后開始真正的分配物理內存,操作系統處理完該錯誤后我們的程序才能真正的讀寫這塊內存。
所以,當我們調用malloc申請內存時:
六、自己實現malloc內存分配器
1.為什么要動態申請內存?
????????因為我們不能提前知道程序到底需要使用多少內存。那我們什么時候才能知道呢?答案是只有當程序真的運行起來后我們才知道。如果能提前知道我們的程序到底需要多少內存,那么直接知道告訴編譯器就好了,這樣也不必發明malloc等內存分配器了。
????????每個進程都認為自己獨占內存。
????????內存動態申請和釋放都發生在堆區,heap。我們使用的malloc或者C++中的new申請內存時,就是從堆區這個區域中申請的。
2.內存分配大致步
(1)跟蹤內存分配狀態
????????將維護內存塊的分配信息保存在內存塊本身中;那么,為了維護內存塊分配狀態,我們需要知道哪些信息呢?很簡單:
- 一個標記,用來標識該內存塊是否空閑
- 一個數字,用來記錄該內存塊的大小
?
????????上圖中:每一方框代表4字節。紅色區域表示已經分配出去的,灰色區域表示空閑內存,每一塊內存都有一個header,用帶斜線的方框表示,比如16/1,就表示該內存塊大小是16字節,1表示已經分配出去了;而32/0表示該內存塊大小是32字節,0表示該內存塊當前空閑。通過每一個header的最后一個bit位就能知道每一塊內存是空閑的還是已經分配出去了,這樣我們就能追蹤到每一個內存塊的分配信息。
????????最后一個方框0/1表示什么呢?原來,我們需要某種特殊標記來告訴我們的內存分配器是不是已經到末尾了,這就是最后4字節的作用。
(2)怎樣選擇空閑內存塊
| 分配策略 | ????????描述 | 優缺點 |
| first fit | 每次從頭開始找起,找到第一個滿足要求的就返回,這就是所謂的First fit方法,教科書中一般稱為首次適應方法。 | 每次從頭開始找起,找到第一個滿足要求的就返回,這就是所謂的First fit方法,教科書中一般稱為首次適應方法 |
| Next Fit | 別總是從頭開始找了,而是從上一次找到合適的空閑內存塊的位置找起 | Next Fit將遠快于First Fit |
| Best Fit | Best Fit算法會找到所有的空閑內存塊,然后將所有滿足要求的并且大小為最小的那個空閑內存塊返回,這樣的空閑內存塊才是最Best的,因此被稱為Best Fit | 缺點:分配內存時需要遍歷堆上所有的空閑內存塊,在速度上顯然不及前面兩種方法。 優點:更合理利用內存 |
(3)內存分配
????????如果空閑內存塊大于所需內存:將空閑內存塊進行劃分,前一部分設置為已分配,返回給內存申請者使用,后一部分變為一個新的空閑內存塊,只不過大小會更小而已。
(4)內存釋放
????????我們要考慮到的關鍵一點就在于,與被釋放的內存塊相鄰的內存塊可能也是空閑的。實際使用的內存分配器都會有某種推遲合并空閑內存塊的策略。
Q:合并內存塊的時候,如何知道上一個內存塊是不是空閑的?
A:我們不是有一個信息頭header嗎,那么我們就在該內存塊的末尾再加一個信息尾,footer,footer一詞用的很形象,header和footer的內容是一樣的。
????????因為上一內存塊的footer和下一個內存塊的header是相鄰的,因此我們只需要在當前內存塊的位置向上移動4直接就可以等到上一個內存塊的信息,這樣當我們釋放內存時就可以快速的進行相鄰空閑內存塊的合并了。
七、內存池技術是如何實現的??
????????malloc性能不高的原因一在于其沒有為特定場景做優化,除此之外還在于malloc看似簡單,但是其調用過程是很復雜的,一次malloc的調用過程可能需要經過操作系統的配合才能完成。既然每次分配內存都要經過這么復雜的過程,那么如果程序大量使用malloc申請內存那么該程序注定無法獲得高性能。
????????幸好,除了大眾貨的malloc,我們還可以私人定制,也就是針對特定場景自己來維護內存申請和分配,這就是高性能高并發必備的內存池技術。
1.內存池技術
????????那malloc和這里提到的內存池技術有什么區別呢?
?
?2.內存池技術原理
????????內存池技術一次性獲取到大塊內存,然后在其之上自己管理內存的申請和釋放,這樣就繞過了標準庫以及操作系統。
3.線程局部存儲+內存池
????????多線程使用內存池,除了使用鎖以外,我們還有別的方案,如:我們為每個線程維護一個內存池就好了,這樣多線程間就不存在競爭問題了。線程局部存儲,Thread Local Storage正是用于解決這一類問題的。
4.內存池形式
????????第一種是提前創建出一堆需要的對象(數據結構),自己維護好哪些對象(數據結構)可用哪些已被分配;
????????第二種可以申請任意大小的內存空間,使用過程中只申請不釋放,最后一次性釋放。前兩種內存池天然適用于服務器端編程。
????????第三種可以提前申請出一大段內存,然后將這一大段內存切分為大小相同的小內存塊,自己來維護(使用棧)這些被切分出來的小內存塊哪些是空閑的哪些是已經被分配的。
?八、C++內存管理全景指南
1.線程與數據競爭
????????一個表達式的求值寫入內存位置,而另一求值讀或寫同一內存位置時,稱這些表達式沖突。擁有二個沖突求值的程序有數據競爭,除非
- 兩個求值都在同一線程上,或同一信號處理函數中執行,或
- 兩個沖突求值都是原子操作(見?std::atomic?),或
- 一個沖突求值先發生于( happens-before )另一個(見內存順序--std::memory_order?)
????????若出現數據競爭,則程序的行為未定義。
2.?C++對象內存模型
????????VS中:先選擇左側的C/C++->命令行,然后在其他選項這里寫上/d1 reportAllClassLayout,它可以看到所有相關類的內存布局,如果寫上/d1 reportSingleClassLayoutXXX(XXX為類名),則只會打出指定類XXX的內存布局。近期的VS版本都支持這樣配置。
(1)空類
class A { };?????????sizeof(A)?=?1,C++標準要求C++的對象大小不能為0,C++對象必須在內存里面有唯一的地址,但又不想浪費太多內存空間,所以標準規定為1byte。
(2)非空類
class A { public:int a; };????????sizeof(A) = 4。
(3)非空虛基類
????????在普通類里面,如果有虛函數的話就會在最開始的地方添加一個隱藏的成員變量,虛函數表指針,然后才到正常的成員變量。
class A { public:int a=10;virtual void v(); };????????sizeof(A) = 16。
3.常見解決內存問題的方法
(1)靜態代碼檢測
- 自動執行靜態代碼分析,快速定位代碼隱藏錯誤和缺陷。
- 幫助代碼設計人員更專注于分析和解決代碼設計缺陷。
- 減少在代碼人工檢查上花費的時間,提高軟件可靠性并節省開發成本。
一些主流的靜態代碼檢測工具:
- ?免費的cppcheck,clang static analyzer;
- 商用的coverity,pclint等?
(2)動態代碼檢測
4.內存池優勢
????????new/delete會造成碎片化,推薦使用內存池。
????????內存池方案通常一次從系統申請一大塊內存塊,然后基于在這塊內存塊可以進行不同內存策略實現,一般采用內存池有以下好處:
?????? 1.少量系統申請次數,非常少(幾沒有) 堆碎片。
?????? 2.由于沒有系統調用等,比通常的內存申請/釋放(比如通過malloc, new等)的方式快。
?????? 3.可以檢查應用的任何一塊內存是否在內存池里。
?????? 4.寫一個”堆轉儲(Heap-Dump)”到你的硬盤(對事后的調試非常有用)。
?????? 5.可以更方便實現某種內存泄漏檢測(memory-leak detection)。
?????? 6.減少額外系統內存管理開銷,可以節約內存。
九、C++成員函數在內存中的存儲方式
class A { public: void printA() { cout<<"printA"<<endl; } virtual void printB() { cout<<"printB"<<endl; } }; int main(void) {A *a=NULL;a->printA();a->printB(); }? ? ? ? 輸出“printA”后,程序崩潰。
????????個人理解:類的非靜態類成員函數其實都內含了一個指向類對象的指針型參數(即this指針),對于普通函數,d->printA(this),printA中沒有使用成員變量,所以不會崩潰。而對于虛函數,存在一個vptr指向虛函數表,但是this不存在,所以this->vptr會崩潰。
????????類的非靜態類成員函數其實都內含了一個指向類對象的指針型參數(即this指針),因而只有類對象才能調用(此時this指針有實值)。
????????new出來的只是成員變量,成員函數始終存在,所以如果成員函數未使用任何成員變量的話,不管是不是static的,都能正常工作。
????????含有虛函數的類中會有一個虛函數指針,本身占用一定空間,虛函數指針是通過查找虛函數表確定調用函數的,如果同時new多個對象,虛函數表可以共用。
????????如果一個類包括了數據和函數,那么我們會以為定義對象時要分別為數據和函數的代碼分配存儲空間。這就錯了,C++編譯系統下,每個對象所占用的存儲空間只是該對象的數據部分(虛函數指針和虛基類指針也屬于數據部分)所占用的存儲空間,而不包括函數代碼所占用的存儲空間。
十、C++ 虛函數表解析
1.虛函數表
????????虛函數表在編譯的時候就確定了,而類對象的虛函數指針vptr是在運行階段確定的,這是實現多態的關鍵!
????????程序構建(Build)的四個過程(預編譯、編譯、匯編和鏈接),推導出虛函數表應該是在編譯期確定的,原因如下:
1)預編譯期主要處理那些源代碼文件中的以“#”開始的預編譯指令,如“#include”、“#define”。很明顯這個過程可以排除。
2)編譯期要做的事情就比較多了,包括詞法分析、語法分析、語義分析及優化代碼等,是整個程序構建的核心。所以,排除了預編譯期、匯編期、鏈接期及考慮到編譯期所做的事情,虛函數表應該是在編譯期建立的。靜態庫在程序編譯時會被連接到目標代碼中。
3)匯編期是將編譯器生成的匯編代碼轉變成機器可以執行的指令,每一個匯編語句幾乎都對應一條機器指令。匯編過程相對于編譯期來說比較簡單,沒有復雜的語法,也沒有語義,也不需要做指令優化,只是根據匯編指令和機器指令的對照表一一翻譯就行了。所以,匯編期也是可以排除的。
4)鏈接期(現只考慮靜態鏈接)是將匯編器生成的目標文件(和庫)鏈接成一個可執行文件,本質上做的是重定位(Relocation)的工作。很明顯鏈接期也是可以排除的。
????????虛函數(Virtual Function)是通過一張虛函數表(Virtual Table)來實現的,簡稱為V-Table。在這個表中,主要是一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其容真實反應實際的函數。這樣,在有虛函數的類的實例中這個表被分配在了這個實例的內存中,所以,當我們用父類的指針來操作一個子類的時候,這張虛函數表就顯得由為重要了,它就像一個地圖一樣,指明了實際所應該調用的函數。
????????C++的編譯器應該是保證虛函數表的指針存在于對象實例中最前面的位置(這是為了保證取到虛函數表的有最高的性能——如果有多層繼承或是多重繼承的情況下)。?這意味著我們通過對象實例的地址得到這張虛函數表,然后就可以遍歷其中函數指針,并調用相應的函數。
2.虛函數&析構函數
? ??????????為什么析構函數必須是虛函數,而C++默認的析構函數不是虛函數?
????????將可能會被繼承的父類的析構函數設置為虛函數,可以保證當我們new一個子類,然后使用基類指針指向該子類對象,釋放基類指針時可以釋放掉子類的空間,防止內存泄漏。
????????C++默認的析構函數不是虛函數是因為虛函數需要額外的虛函數表和虛表指針,占用額外內存。而對于不會被繼承的類來說,就會浪費內存。因此C++默認的析構函數不是虛函數,而是只有當需要當作父類時,設置為虛函數。
3.虛函數&繼承
(1)一般繼承(沒有overwrite)
? ? ? ? ? ?????
(2)一般繼承(有虛函數覆蓋)
??????????????
(3)多重繼承(無虛函數覆蓋)
? ? ? ???
(4)多重繼承(有虛函數覆蓋)
????
十一、為什么SSD不能當做內存用??
1.內存條和固態硬盤的區別
(1)內存條
????????主要提供數據中轉的功能。打開軟件后,因為CPU直接讀取固態硬盤上的數據非常慢,CPU會先將要用到的數據從固態遷移到內存,內存條的運行速度更快,進而提高運行效率。關機后內存條上的數據無法保存。
(2)固態硬盤(SSD)
????????是用來真正存儲數據的地方,俗稱的“C盤”、“D盤”……都是來自固態硬盤。
????????固態硬盤是增加系統和軟件的運行速度,比如打開一個軟件就要快很多。內存是增加電腦多任務處理的能力,比如你玩游戲的時候聽歌或者聊天,內存小的電腦處理起來會有些卡頓。
2.CPU是怎么訪問文件內容的呢?
????????文件系統把SSD上的數據以文件的形式呈現出來,程序直接操作文件,讀寫文件時把請求發送給文件系統,文件系統把請求路由給SSD,SSD處理完請求后數據會被copy到相應進程的內存中,此后程序直接操作內存。
????????CPU無法直接按照字節粒度去訪問SSD,因此CPU無法脫離內存直接在SSD中運行你寫的程序。
3.SSD壽命短
????????SSD的制造原理決定了這類存儲設備是有固定使用壽命的。SSD有TBW(Max Terabytes Written,總寫入字節)這個限制的,內存則沒有這個問題。
????????因此如果你把SSD當內存用的話,相信很快你的SSD就會被CPU寫死。
4.總結
????????我們還沒有辦法直接把SSD當做內存來用,我們的各種軟件包括操作系統、文件系統以及各種硬件包括CPU等都沒有做好把SSD當做內存來用的準備。
????????更新補充:Intel推出的傲騰持久內存,Intel Optane Persistent Memory ,這種硬件可以當做內存來用,和普通內存一樣,但同時也具有非易失特性,像磁盤或者SSD一樣斷電后內存中的數據不會丟失。
十二、10 個內存引發的大坑,你能躲開幾個???
1.malloc后要初始化
當調用 malloc 時實際上有以下兩種可能:
2.malloc后要free
int* p = (int*)malloc(sizeof(int)*1024*1024);//4Mmemset(p, 0, sizeof(int) * 1024 * 1024);std::cout << *p << std::endl;free(p);總結
- 上一篇: CAD常见的20个问答
- 下一篇: amazon - amzreport 之