我們已經(jīng)從前面一篇《第3篇-C/C++ 類和內(nèi)存分配(前)》的一些詳細(xì)例子了解到new操作符的基本用法。
那么,我們現(xiàn)在需要知道在new操作符的底層旬盯,C++編譯器到底做了些什么宝与?首先,new操作符的實(shí)質(zhì)就是一個(gè)函數(shù),但這個(gè)函數(shù)比較特殊,它帶有operator關(guān)鍵字,C++編譯器會對他們做特殊處理礼仗。
new operator函數(shù)原型
void* operator new(size_t size);
因此,我們知道new操作符會返回一個(gè)void指針,void指針是什么虐急?就是萬能指針,在C/C++世界中,你可以理解為面向?qū)ο?/strong>的“造物主”,只要給定一個(gè)無符號大于0的整數(shù),C/C++用該數(shù)據(jù)塊尺寸解析為不同數(shù)據(jù)類型比庄。這就是C/C++中,面向?qū)ο蟮谋驹础D敲磸腃/C++中衍生出來的其他高層語言的琳瑯滿目的面相對象技術(shù)研乒,追根溯源也是void*指針和對應(yīng)的數(shù)據(jù)尺寸汹忠。只不過他們虛擬機(jī)或者編譯器隱藏了這些更底層的操作而已。
備注:標(biāo)準(zhǔn)庫中帶有operator關(guān)鍵字的函數(shù),我們叫做operator函數(shù)雹熬。推而廣之,所有常用的操作符例如I/O中常用的“<<”,“>>”,“endl”都是operator函數(shù)宽菜。
new的底層操作
我們用前文的示例Student類作為一個(gè)示例
typedef struct _student
{
double score; //8
double height; //8
char name[6]; //6
unsigned int sid; //4
char sex; //1
} Student;
那么當(dāng)我們new一個(gè)Student的時(shí)候,即以下語句
Student *st=new Student();
-
第一步:給指定數(shù)據(jù)類型尺寸分配內(nèi)存
從C++編譯器來說橄唬,它會隱式調(diào)用以下operator函數(shù),并且傳遞了. sizeof(Student)計(jì)算的內(nèi)存塊大小,之前說過是32個(gè)字節(jié)赋焕。void* raw=operator new(sizeof(Student));
st指針獲得了Student對象的原始內(nèi)存,也就是st指向原始內(nèi)存的首個(gè)字節(jié)的內(nèi)存地址。
-
第二步:調(diào)用指定數(shù)據(jù)類型的構(gòu)造函數(shù)并且初始化已分配(堆)內(nèi)存中的對象仰楚。類似下面的偽代碼, 因?yàn)闆]有自定義的構(gòu)造函數(shù),編譯器會使用默認(rèn)構(gòu)造Student,即對象中的數(shù)據(jù)成員都會執(zhí)行默認(rèn)值初始化。
call Student::Student();
然后犬庇,執(zhí)行類型轉(zhuǎn)換將void指針raw僧界,轉(zhuǎn)換為類型為Student對象的指針類型。
Student *st=static_cast<Student*>(raw);
對象中的數(shù)據(jù)成員
假設(shè)我們的環(huán)境是x86_64,即當(dāng)我們嘗試通過Student的對象指針遍歷對象內(nèi)部的數(shù)據(jù)成員,那么C++編譯器從剛才分配的內(nèi)存空間的首個(gè)字節(jié)的地址st開始讀取,并且每次8個(gè)字節(jié)依次讀取臭挽,加載到寄存器做某些處理捂襟。
假設(shè)我們要訪問st的sid屬性,那么C++編譯器做了些什么操作呢?
Student *st=new Student();
...
st->sid
C++編譯器會執(zhí)行如下操作欢峰。
- 從st指針的位置向高地址相對偏移24個(gè)字節(jié)葬荷,
- 從st+24內(nèi)存地址開始按照8個(gè)字節(jié)為單位加載到寄存器(一般是RAX寄存器)
- 在寄存器中向低地址方向shift四個(gè)字節(jié),最終讀取sid四個(gè)字節(jié)的值纽帖。
以下是兩點(diǎn)寄語:
- 在C++中,盡量不要使用malloc/calloc等C版本的堆內(nèi)存分配API宠漩,一旦你這么做,然后又實(shí)用delete操作符的話,那么就會造成內(nèi)存泄漏懊直。
- 同理:也不要在C++中使用C版本的free()去釋放由new操作符分配的堆內(nèi)存,否則會造成內(nèi)存泄漏扒吁。
結(jié)語:
當(dāng)我們理解了new的操作符后,可以執(zhí)行更加高級的C++操作室囊,就是重寫C++的void* operator new(size_t)的原型雕崩,但大部分情況下魁索,我們其實(shí)用默認(rèn)版本的new足夠我們?nèi)粘i_發(fā)的需要,反而我們更多需要重寫的是delete操作符和delete[]操作符盼铁。
如果你覺得我的文章對你有所幫助的粗蔚,可以關(guān)注我,并且分享給你的其他圈子饶火,但請著名出處支鸡。