某日二師兄參加XXX科技公司的C++工程師開發崗位6面: > 面試官: 如何在堆上申請一塊記憶體? > > 二師兄:常用的方法有malloc,new等。 > > 面試官:兩者有什麼區別? > > 二師兄:malloc是向操作系統申請一塊記憶體,這塊記憶體沒有經過初始化,通常需要使用memset手動初始化。 ...
某日二師兄參加XXX科技公司的C++工程師開發崗位6面:
面試官: 如何在堆上申請一塊記憶體?
二師兄:常用的方法有malloc,new等。
面試官:兩者有什麼區別?
二師兄:malloc是向操作系統申請一塊記憶體,這塊記憶體沒有經過初始化,通常需要使用memset手動初始化。而new一般伴隨三個動作,向操作系統申請一塊記憶體,並執行類型的預設構造函數,然後返回類的指針。
面試官:嗯,那你知道calloc和realloc嗎?
二師兄:calloc比malloc多做了一步,就是把申請的記憶體初始化成0。而realloc則可以改變當前指針所指向的記憶體塊的大小。
面試官:好的。那麼你知道這些api/操作符失敗會發生什麼嗎?
二師兄:malloc/calloc/realloc失敗會返回NULL,而new失敗則會拋出異常。
面試官:有沒有讓new失敗不拋出異常的方法?
二師兄:好像有,但是我不記得了。。。
面試官:沒關係。。。我們都知道new和delete成對出現,new[]和delete[]也是成對出現,那麼我想問,如果使用new[]創建的對象用delete釋放了會發生什麼?為什麼?
二師兄:額。。。記憶體泄漏?對,會發生記憶體泄漏。因為記憶體沒有被釋放。
面試官:好的。我們都知道C++中的記憶體管理是一個比較麻煩的事情,現在有個需求,需要在程式中記錄主動申請的記憶體和主動釋放的記憶體,以確保沒有發生記憶體泄漏。有什麼好的方法嗎?
二師兄:可以重載new和delete運算符。
面試官:如何重載new和delete運算符?
二師兄:我得查一下資料,這個重載用的很少。。。
面試官:(笑)好吧,最後一個問題,咱們上面一直在討論堆中的記憶體的分配和釋放,請問一下,如果在棧上分配一塊固定的記憶體?棧中的記憶體如何釋放?
二師兄:額。。。(思考)使用 char[size] ? 應該不需要手動釋放。
面試官:好的,回去等通知吧。
對於二師兄的表現,小伙伴們能給打幾分呢?我們先看看二師兄在面試中表現不太好的地方:
面試官:有沒有讓new失敗不拋出異常的方法?
在C++中我們可以使用以下方法使得new運算符不拋出異常,
int* p = new (std::nothrow) int(42);
if(p == nullptr)
{
//分配失敗
}
這個特性需要C++11支持。
再看下一個問題:
如果使用new[]創建的對象用delete釋放了會發生什麼?
一定會發生記憶體泄漏嗎?答案是,不一定。這取決於類型T。我們先看第一種情況:
class Foo
{
public:
Foo():num_(42){}
private:
int num_;
};
Foo* pf = new Foo[1024];
delete pf;
當類型T沒有管理資源時,delete pf會把整個申請的1024個Foo所占用的記憶體全部歸還給操作系統,此時並沒有記憶體泄漏。再看下一種情況:
class Foo
{
public:
Foo():num_(new int(42)){}
~Foo(){delete num_;}
private:
int* num_;
};
Foo* pf = new Foo[1024];
delete pf;
此時會造成記憶體泄漏,原因很簡單。在執行delete[]時,首先逆序執行每個元素的析構函數,然後再把整塊記憶體歸還給操作系統。而delete只會把記憶體還給操作系統,沒有執行析構函數。當類沒有資源需要管理時,執行與不執行析構函數都無關緊要,但是當類中需要管理資源時,析構函數的執行就至關重要了。
如何重載new和delete運算符?
#include <iostream>
#include <cstdlib>
#include <map>
struct MemoryInfo {
size_t size;
const char* file;
int line;
};
std::map<void*, MemoryInfo> memoryMap;
void* operator new(size_t size, const char* file, int line) {
void* ptr = std::malloc(size);
memoryMap[ptr] = {size, file, line};
return ptr;
}
void operator delete(void* ptr) noexcept {
auto it = memoryMap.find(ptr);
if (it != memoryMap.end()) {
std::free(ptr);
memoryMap.erase(it);
}
}
#define new new(__FILE__, __LINE__)
int main() {
int* p = new int(42);
for (const auto& [ptr, info] : memoryMap) {
std::cout << "Memory allocated at " << ptr << " with size " << info.size
<< " in file " << info.file << " at line " << info.line << std::endl;
}
delete p;
for (const auto& [ptr, info] : memoryMap) {
std::cout << "Memory allocated at " << ptr << " with size " << info.size
<< " in file " << info.file << " at line " << info.line << std::endl;
}
return 0;
}
最後一個問題:
如果在棧上分配一塊固定的記憶體?棧中的記憶體如何釋放?
使用alloca,雖然簡單,但是很多人可能都沒有接觸過:
int* p = (int*)alloca(4);
*p = 42;
棧上申請的記憶體不需要手動釋放。註意,如果棧溢出,alloca的行為時未定義的。
好了,今日份面試到這裡就結束了,小伙伴們,對於今天二師兄的面試,能打幾分呢?
關註我,帶你21天“精通”C++!(狗頭)