簡體   English   中英

自動內存分配如何在C ++中實際工作?

[英]How does automatic memory allocation actually work in C++?

在C ++中,假設沒有優化,那么以下兩個程序最終會使用相同的內存分配機器代碼嗎?

int main()
{     
    int i;
    int *p;
}

int main()
{
    int *p = new int;
    delete p;
}

為了更好地理解正在發生的事情,讓我們假設我們只有一個非常原始的操作系統在一個16位處理器上運行,一次只能運行一個進程。 這就是說:一次只能運行一個程序。 此外,讓我們假裝所有中斷都被禁用。

我們的處理器中有一個稱為堆棧的構造。 堆棧是強加於物理內存的邏輯構造。 假設我們的RAM存在於地址E000到FFFF中。 這意味着我們正在運行的程序可以按照我們想要的方式使用這個內存。 讓我們假設我們的操作系統說E000到EFFF是堆棧,而F000到FFFF是堆。

堆棧由硬件和機器指令維護。 我們沒有太多需要做的來維護它。 我們(或我們的操作系統)需要做的就是確保為堆棧的開始設置正確的地址。 堆棧指針是物理實體,駐留在硬件(處理器)中並由處理器指令管理。 在這種情況下,我們的堆棧指針將設置為EFFF(假設堆棧增長了BACKWARDS,這很常見, - )。 使用像C這樣的編譯語言,當你調用一個函數時,它會將你傳入的任何參數推送到堆棧上的函數中。 每個參數都有一定的大小。 int通常是16位或32位,char通常是8位,等等。讓我們假裝在我們的系統上,int和int *是16位。 對於每個參數,堆棧指針由sizeof(參數)為DECREMENTED( - ),並且參數被復制到堆棧中。 然后,您在范圍中聲明的任何變量都以相同的方式被壓入堆棧,但它們的值未初始化。

讓我們重新考慮兩個與你的兩個例子相似的例子。

int hello(int eeep)
{
    int i;
    int *p;
}

在我們的16位系統上發生的事情如下:1)將eeep推入堆棧。 這意味着我們將堆棧指針遞減到EFFD(因為sizeof(int)是2)然后實際復制eeep以解決EFFE(堆棧指針的當前值減去1,因為我們的堆棧指針指向可用的第一個點分配后)。 有時會有一些指令可以一舉做到(假設您正在復制適合寄存器的數據。否則,您必須手動將數據類型的每個元素復制到堆棧中的適當位置 - 順序! )。

2)為我創造空間。 這可能意味着只是將堆棧指針遞減到EFFB。

3)為p創建空間。 這可能意味着只是將堆棧指針遞減到EFF9。

然后我們的程序運行,記住我們的變量存在的位置(eeep從EFFE開始,我在EFFC,而在EFFA開始)。 要記住的重要一點是,即使堆棧計數BACKWARDS,變量仍然運行FORWARDS(這實際上取決於字節序,但重點是&eeep == EFFE,而不是EFFF)。

當函數關閉時,我們簡單地將堆棧指針遞增(++)6,(因為2個大小為2的3“對象”而不是c ++類型已經被壓入堆棧。

現在,你的第二個場景要難以解釋,因為實現它的方法太多了,幾乎不可能在互聯網上解釋。

int hello(int eeep)
{
    int *p = malloc(sizeof(int));//C's pseudo-equivalent of new
    free(p);//C's pseudo-equivalent of delete
}

仍然按照前面的示例推送和分配堆棧中的eeep和p。 但是,在這種情況下,我們將p初始化為函數調用的結果。 什么malloc(或者新的,但是新的在c ++中做的更多。它在適當的時候調用構造函數,以及其他所有東西。)確實是這個黑盒子叫做HEAP並得到一個空閑內存的地址。 我們的操作系統將為我們管理堆,但是我們必須讓它知道我們何時需要內存以及何時完成它。

在這個例子中,當我們調用malloc()時,操作系統將返回一個2字節的塊(我們系統上的sizeof(int)為2),給出這些字節的起始地址。 假設第一個電話給了我們地址F000。 操作系統隨后跟蹤當前正在使用的地址F000和F001。 當我們調用free(p)時,OS找到p指向的內存塊,並將2個字節標記為未使用(因為sizeof(star p)為2)。 相反,如果我們分配更多內存,地址F002可能會作為新內存的起始塊返回。 請注意,malloc()本身就是一個函數。 當p被推入堆棧以進行malloc()的調用時,p會在第一個打開的地址處再次復制到堆棧上,堆棧上有足夠的空間來適應p的大小(可能是EFFB,因為我們只推了2個這次大小為2的堆棧上的東西,sizeof(p)是2),堆棧指針再次遞減到EFF9,malloc()將把它的局部變量放在堆棧中,從這個位置開始。 當malloc完成時,它會將所有項目從堆棧中彈出,並將堆棧指針設置為調用它之前的內容。 malloc()(一個void星)的返回值可能會放在某個寄存器(通常是許多系統上的累加器)中供我們使用。

在實現中,兩個示例真的不是這么簡單。 當您分配堆棧內存時,對於新的函數調用,您必須確保保存狀態(保存所有寄存器),以便新函數不會永久擦除值。 這通常也會將它們推到堆疊上。 同樣,您通常會保存程序計數器寄存器,以便在子程序返回后返回正確的位置。 內存管理器會耗盡自己的內存,以“記住”已經發出的內存和沒有內存的內存。 虛擬內存和內存分段使這個過程變得更加復雜,並且內存管理算法必須不斷地移動塊(並保護它們)以防止內存碎片(它自己的整個主題),這與虛擬內存相關聯同樣。 與第一個例子相比,第二個例子確實是一大堆蠕蟲。 此外,運行多個進程會使所有這些變得更加復雜,因為每個進程都有自己的堆棧,並且堆可以被多個進程訪問(這意味着它必須保護自己)。 此外,每個處理器架構都不同。 一些架構希望您將堆棧指針設置為堆棧上的第一個空閑地址,其他架構將指望您將其指向第一個非空閑點。

我希望這有幫助。 請告訴我。

請注意,上述所有示例都適用於過度簡化的虛構機器。 在真正的硬件上,這會變得更加毛茸茸。

編輯:星號未顯示。 我用“星”這個詞取而代之


對於它的價值,如果我們在示例中使用(大多數)相同的代碼,分別用“example1”和“example2”替換“hello”,我們在wndows上得到以下的intel輸出組件輸出。

.file   "test1.c"
    .text
.globl _example1
    .def    _example1;  .scl    2;  .type   32; .endef
_example1:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $8, %esp
    leave
    ret
.globl _example2
    .def    _example2;  .scl    2;  .type   32; .endef
_example2:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $8, %esp
    movl    $4, (%esp)
    call    _malloc
    movl    %eax, -4(%ebp)
    movl    -4(%ebp), %eax
    movl    %eax, (%esp)
    call    _free
    leave
    ret
    .def    _free;  .scl    3;  .type   32; .endef
    .def    _malloc;    .scl    3;  .type   32; .endef

不,沒有優化......

int main() 
{      
    int i; 
    int *p; 
}

什么也沒做 - 只是調整堆棧指針的幾條指令,但是

int main() 
{ 
    int *p = new int; 
    delete p; 
}

在堆上分配一塊內存然后釋放它,這是一大堆工作(我在這里很認真 - 堆分配不是一個簡單的操作)。

    int i;
    int *p;

^在堆棧上分配一個整數和一個整數指針

int *p = new int;
delete p;

^在堆棧上分配一個整數指針,在堆上分配整數大小的塊

編輯:

堆棧段和堆段之間的差異

替代文字
(來源: maxi-pedia.com

void another_function(){
   int var1_in_other_function;   /* Stack- main-y-sr-another_function-var1_in_other_function */
   int var2_in_other_function;/* Stack- main-y-sr-another_function-var1_in_other_function-var2_in_other_function */
}
int main() {                     /* Stack- main */
   int y;                        /* Stack- main-y */
   char str;                     /* Stack- main-y-sr */
   another_function();           /*Stack- main-y-sr-another_function*/
   return 1 ;                    /* Stack- main-y-sr */ //stack will be empty after this statement                        
}

每當任何程序開始執行時,它將所有變量存儲在稱為Stack segment的特殊memoy內存位置。 例如,在C / C ++的情況下,第一個被調用的函數是main。 所以它將首先放在堆棧上。 main中的任何變量都將在程序執行時置於堆棧中。 現在main是第一個被調用的函數,它將是最后一個返回任何值的函數(或者將從堆棧中彈出)。

現在,當您使用new另一個特殊內存位置動態分配內存時,使用稱為Heap segment的內存。 即使堆指針上存在實際數據也位於堆棧上。

聽起來你不知道堆棧和堆。 你的第一個例子是在堆棧上分配一些內存,一旦超出范圍就會被刪除。 使用malloc / new獲取的堆上的內存將保持不變,直到您使用free / delete刪除它。

在第一個程序中,您的變量都駐留在堆棧中。 你沒有分配任何動態內存。 'p'只是坐在堆棧上,如果你取消引用它,你會得到垃圾。 在第二個程序中,您實際上是在堆上創建一個整數值。 在這種情況下,'p'實際上指向一些有效的內存。 您實際上可以取消引用p並將其設置為安全有意義的內容:

*p = 5;

這在第二個程序(刪除前)中有效,而不是第一個。 希望有所幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM