文章目錄
- 1. c++內存管理
- 2. 堆與棧
- 3.變量定義與生命周期
- 4.內存對齊
- 5.內存泄露
- 6.智能指針
- 7.new 和 malloc 有什么區別
- 8.delete和free的區別
- 9.什么野指針,怎么產生的,如何避免野指針
- 10.野指針和指針懸浮的區別
- 11.字符串操作函數
- 參考
1. c++內存管理
c++在運行程序時,內存被分為幾個不同的區域,每個區域負責不同的任務。c++程序使用的分區一般包括:棧區、堆區、全局/靜態變量存儲區、常量存儲區、代碼區。
區域名 | 存放的數據 | 生命周期 |
---|---|---|
棧區 | 局部變量、函數參數、函數返回值 | 棧上的變量?命周期與其所在函數的執?周期相同 |
堆區 | 動態分配的內存new、malloc | ?命周期由程序員顯式控制 |
全局/靜態存儲區 | 全局變量和靜態變量 | 整個程序運行期間 |
常量區 | 常量,不允許修改 | 程序運行結束自動釋放 |
代碼區 | 代碼 |
- 棧區:
- 由編譯器自動存放和釋放,棧空間在進程生存周期一直都存在,當進程退出時,操作系統才會對棧空間進行回收。
- 存放局部變量 、函數參數、函數返回值;
- 函數的調用和返回通過棧來管理;
局部變量:
在函數或一個代碼塊內部聲明的變量,稱為局部變量。
它們只能被函數內部或者代碼塊內部的語句使用。
-
堆區:
- 堆?于存儲動態分配的內存的區域,由程序員?動分配和釋放;
- 使? new 和 delete 或 malloc 和 free 來進?堆內存的分配和釋放;
-
全局/靜態存儲區:
- ?命周期是整個程序運?期間;
- 在程序啟動時分配,程序結束時釋放;
- 全局區存儲全局變量和靜態變量;
全局變量:
在所有函數外部定義的變量(通常是在程序的頭部),稱為全局變量。
全局變量的值在程序的整個生命周期內都是有效的。靜態變量:
在函數體內聲明一個靜態局部變量( Static Local Variable )。
它在函數運行結束后不會消失,并且只有聲明它的函數中能夠使用它。
聲明一個靜態局部變量的方法是在聲明局部變量前加上 static。
-
常量區:
- 存放的是常量,不允許修改,程序運行結束自動釋放。
-
代碼區:
- 存放代碼,不允許修改,但可以執行。編譯后的二進制文件存放在這里。
示例:
#include <iostream>
using namespace std;
/*
說明:C++ 中不再區分初始化和未初始化的全局變量、靜態變量的存儲區,如果非要區分下述程序標注在了括號中
*/
int g_var = 0; // g_var 在全局區(.data 段)
char *gp_var; // gp_var 在全局區(.bss 段)int main()
{int var; // var 在棧區char *p_var; // p_var 在棧區char arr[] = "abc"; // arr 為數組變量,存儲在棧區;"abc"為字符串常量,存儲在常量區char *p_var1 = "123456"; // p_var1 在棧區;"123456"為字符串常量,存儲在常量區static int s_var = 0; // s_var 為靜態變量,存在靜態存儲區(.data 段)p_var = (char *)malloc(10); // 分配得來的 10 個字節的區域在堆區free(p_var);return 0;
}作者:LeetCode
鏈接:https://leetcode.cn/leetbook/read/cmian-shi-tu-po/vv6a76/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
2. 堆與棧
棧和堆都是?于存儲程序數據的內存區域。棧是?種有限的內存區域,?于存儲局部變量、函數調?信息等。
堆是?種動態分配的內存區域,?于存儲程序運?時動態分配的數據。
棧上的變量?命周期與其所在函數的執?周期相同,?堆上的變量?命周期由程序員顯式控制,可以(使? new 或 malloc )和釋放(使? delete 或 free )。
棧上的內存分配和釋放是?動的,速度較快。?堆上的內存分配和釋放需要?動操作,速度相對較慢。
- 申請方式:棧中存放的變量在編譯時由編譯器為其在棧上分配了空間,釋放時也由于函數調用的返回,棧的空間會自動進行回收。堆中存放的變量由程序運行時決定的,會有操作系統或者內存管理模塊來進行分配的。
3.變量定義與生命周期
c/c++變量有兩個非常重要的屬性生命周期和作用域,這兩個屬性分別從時間和空間兩個維度描述一個變量。
-
作用域:作用域即一個變量可以被引用的范圍,常見的作用域可分為 6 種:全局作用域,局部作用域,語句作用域,類作用域,命名空間作用域和文件作用域。
- 全局變量:具有全局作用域。只需要在一個源文件里定義,就可以作用于所有源文件。其他不包含全局變量定義的源文件,需要用extern關鍵字在次聲明全局變量 。
- 靜態全局變量:具有文件作用域。它作用于定義它的文件里,不能作用到其它文件里,即被 static 關鍵字修飾過的變量具有文件作用域。這樣即使兩個文件里定義了相同名字的全局靜態變量,它們也不是相同的變量。
- 局部變量:具有局部作用域。僅作用在函數內部,對于函數外部的程序來說不可見。
- 局部靜態變量:具有局部作用域,它只被初始化一次,只對定義自己的函數體始終可見, 只有定義該變量的函數內部可以使用訪問和修改該變量。
-
生命周期:變量可以被引用的時間段(變量的存在時間)
- 全局變量:整個程序運行期間都會一直存在,都可以隨時訪問,當程序結束時,對應的變量則會自動銷毀,內存會被系統回收。
- 局部變量:生命周期僅限于函數被調用階段,調用結束,該變量會自動銷毀。
- 局部靜態變量:整個程序運行期間一直存在,只能被初始化一次。
-
注意:
- 全局變量定義在不要在頭文件中定義:如果在頭文件中定義全局變量,當該頭文件被多個文件 include 時,該頭文件中的全局變量就會被定義多次,編譯時會因為重復定義而報錯,因此不能再頭文件中定義全局變量。一般情況下我們將變量的定義放在 .cpp 文件中,一般在 .h 文件使用extern 對變量進行聲明。
4.內存對齊
- 什么是內存對齊:內存對?是指數據在內存中的存儲起始地址是某個值的倍數。
- ?多數計算機硬件要求基本數據類型的變量在內存中的地址是它們??的倍數。例如,?個 32 位整數通常需要在內存中對?到 4 字節邊界。
- 內存對?可以提?訪問內存的速度。當數據按照硬件要求的對??式存儲時,CPU可以更?效地訪問內存,減少因為不對??引起的性能損失。
- 許多計算機體系結構使?緩存?(cache line)來從內存中加載數據到緩存中。如果數據是對?的,那么?個緩存?可以裝載更多的數據,提?緩存的命中率。
- 有些計算機架構要求原?性操作(?如原?性讀寫)必須在特定的內存地址上執?。如果數據不對?,可能導致?法執?原?性操作,進?引發競態條件。
5.內存泄露
- 什么是內存泄露:內存泄漏并非指內存從物理上消失,而是指程序在運行過程中,由于疏忽或錯誤而失去了對該內存的控制,從而造成了內存的浪費。
- 內存分類:
- 堆內存泄漏:堆是動態分配的,一旦用戶申請了內存分配而為及時釋放,那么該部分內存在整個程序運行周期內都是被占用的,其他程序無法再使用這部分內存。我們在調用過程中使用 malloc、calloc、realloc、new 等分配內存時,使用完后要調用相應的 free 或 delete 釋放內存,否則這塊內存就會造成內存泄漏。
- 系統資源泄露:主要指程序使?系統分配的資源?如 Bitmap,handle ,SOCKET 等沒有使?相應的函數釋放掉,導致系統資源的浪費,嚴重可導致系統效能降低,系統運?不穩定。
- 沒有將基類的析構函數定義為虛函數:
當基類指針指向?類對象時,如果基類的析構函數不是 virtual,那么?類的析構函數將不會被調?,?類的資源沒有正確是釋放,因此造成內存泄露。
- 什么操作導致內存泄漏:指針指向改變,未釋放動態分配內存。
- 如何防止內存泄漏:將內存的分配封裝在類中,構造函數分配內存,析構函數釋放內存;使?智能指針。
- 智能指針有了解哪些:智能指針是為了解決動態分配內存導致內存泄露和多次釋放同?內存所提出的,C11標準中放在<memory>頭?件。包括:共享指針,獨占指針,弱指針。
- 構造函數,析構函數要設為虛函數嗎,為什么?
(1)析構函數
析構函數需要。當派?類對象中有內存需要回收時,如果析構函數不是虛函數,不會觸發動態綁定,只會調?基類析構函數,導致派?類資源?法釋放,造成內存泄漏。
(2)構造函數
構造函數不需要,沒有意義。虛函數調?是在部分信息下完成?作的機制,允許我們只知道接??不知道對象的確切類型。 要創建?個對象,你需要知道對象的完整信息。 特別是,你需要知道你想要創建的確切類型。 因此,構造函數不應該被定義為虛函數。
6.智能指針
智能指針?于管理動態內存的對象,其主要?的是在避免內存泄漏和?便資源管理。
- std::unique_ptr 獨占智能指針
- std::shared_ptr 共享智能指針
- std::weak_ptr 弱引?智能指
7.new 和 malloc 有什么區別
new | malloc | |
---|---|---|
類型安全性 | 是C++的運算符,可以為對象分配內存并調?相應的構造函數。 | 是C語?庫函數,只分配指定??的內存塊,不會調?構造函數。 |
返回值 | 返回的是具體類型的指針,?且不需要進?類型轉換。 | 返回的是 void* ,需要進?類型轉換,因為它不知道所分配內存的?途。 |
內存分配失敗 | 在內存分配失敗時會拋出 std::bad_alloc 異常。 | 在內存分配失敗時返回 NULL 。 |
內存塊大小 | 可以?于動態分配數組,并知道數組??。 | 只是分配指定??的內存塊,不了解所分配內存塊的具體?途。 |
釋放內存方式 | 會調?對象的析構函數,然后釋放內存。 | 只是簡單地釋放內存塊,不會調?對象的析構函數。 |
8.delete和free的區別
delete | free | |
---|---|---|
類型安全性 | 會調?對象的析構函數,確保資源被正確釋放。 | 不了解對象的構造和析構,只是簡單地釋放內存塊。 |
內存塊釋放后的?為 | 釋放的內存塊的指針值會被設置為 nullptr ,以避免野指針。 | 不會修改指針的值,可能導致野指針問題。 |
數組的釋放 | 可以正確釋放通過 new[] 分配的數組。 | 不了解數組的??,不適?于釋放通過 malloc 分配的數組 |
9.什么野指針,怎么產生的,如何避免野指針
野指針是指指向已被釋放的或?效的內存地址的指針。使?野指針可能導致程序崩潰、數據損壞或其他不可預測的?為。
怎么產生的野指針 | 真么避免野指針 |
---|---|
指針沒有初始化 | 指針變量一定要初始化,可以初始化為nullptr |
釋放后沒有置空指針 | 在釋放內存后將指針置為 nullptr |
返回局部變量的指針 | 避免返回局部變量的指針 |
函數參數指針被釋放 | 避免函數參數指針被釋放 |
使?智能指針(如 std::unique_ptr 和 std::shared_ptr ) | |
注意函數參數的?命周期, 避免在函數內釋放調??傳遞的指針,或者通過引?傳遞指針 |
10.野指針和指針懸浮的區別
野指針是指向已經被釋放或者?效的內存地址的指針。通常由于指針指向的內存被釋放,但指針本身沒有被置為nullptr 或者重新分配有效的內存,導致指針仍然包含之前的內存地址。使?野指針進?訪問會導致未定義?為,可能引發程序崩潰、數據損壞等問題。
懸浮指針是指向已經被銷毀的對象的引?。當函數返回?個局部變量的引?,?調?者使?該引?時,就可能產?懸浮引?。訪問懸浮引?會導致未定義?為,因為引?指向的對象已經被銷毀,數據不再有效。
- 區別:
野指針 | 懸浮指針 | |
---|---|---|
關聯對象類型 | 涉及指針類型 | 設計引用類型 |
問題表現 | 可能導致訪問已釋放或?效內存,引發崩潰或數據損壞 | 可能導致訪問已銷毀的對象,引發未定義?為 |
產?原因 | 野指針通常由于不正確管理指針?命周期引起 | 懸浮指針通常由于在函數中返回局部變量的引?引起 |
如何避免懸浮指針 | 避免在函數中返回局部變量的引? |
11.字符串操作函數
-
strcpy(): char * strcpy(char *dest, const char *src)
把從src地址開始且含有’\0’結束符的字符串復制到以dest開始的地址空間,返回值的類型為char* -
strlen():size_t strlen (const char *s)
計算給定字符串的?度。 -
strcat():char * strcat(char *dest, const char *src)
作?是把src所指字符串添加到dest結尾處。 -
strcmp():int strcmp(const char *s1, const char *s2)
?較兩個字符串設這兩個字符串為s1,s2,
若s1 == s2,則返回零
若s1 < s2,則返回負數
若s1 > s2,則返回正數
參考
Leetcodec++面試突破
代碼隨想錄——最強八股文