性无码一区二区三区在线观看,少妇被爽到高潮在线观看,午夜精品一区二区三区,无码中文字幕人妻在线一区二区三区,无码精品国产一区二区三区免费

徐土豆
認(rèn)證:優(yōu)質(zhì)創(chuàng)作者
所在專題目錄 查看專題
[C語(yǔ)言朝花夕拾] C語(yǔ)言中的命令行輸入?yún)?shù)判斷
用“位操作”取代“取模操作”判斷奇數(shù)偶數(shù)
c語(yǔ)言運(yùn)行時(shí)出現(xiàn)segment fault的原因
一文理解C語(yǔ)言中的volatile修飾符
C語(yǔ)言中的內(nèi)存布局(memory layout)
do{}while(false)結(jié)構(gòu)的妙用
作者動(dòng)態(tài) 更多
給定計(jì)算預(yù)算下的最佳LLM模型尺寸與預(yù)訓(xùn)練數(shù)據(jù)量分配
05-19 09:33
大模型推理時(shí)的尺度擴(kuò)展定律
05-18 10:32
世界多胞體與世界模型
05-13 09:42
獎(jiǎng)勵(lì)模型中的尺度擴(kuò)展定律和獎(jiǎng)勵(lì)劫持
05-12 08:41
MeCo——給預(yù)訓(xùn)練數(shù)據(jù)增加源信息,就能減少33%的訓(xùn)練量并且提升效果
05-08 09:13

C語(yǔ)言中的內(nèi)存布局(memory layout)

本文轉(zhuǎn)自徐飛翔的“C語(yǔ)言中的內(nèi)存布局(memory layout)

版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接和本聲明。

內(nèi)存布局

根據(jù)經(jīng)典的計(jì)算機(jī)馮洛伊曼模型,內(nèi)存儲(chǔ)存著計(jì)算過(guò)程中的代碼和數(shù)據(jù)等。一般來(lái)說(shuō),內(nèi)存是稱之為DRAM,其數(shù)據(jù)是掉電易失的,我們?yōu)榱撕?jiǎn)化編程過(guò)程,通常會(huì)把內(nèi)存空間當(dāng)作是連續(xù)的一大塊,也就是說(shuō)如果給每個(gè)內(nèi)存小塊進(jìn)行編址的話,可以從0直接編碼到最大的內(nèi)存空間上限,我們通常把這個(gè)一大塊連續(xù)的內(nèi)存空間稱之為虛擬內(nèi)存空間,為什么稱之為“虛擬”呢?那是因?yàn)槲锢碛布系膬?nèi)存上不一定是連續(xù)的,其通過(guò)了一系列的映射才把可能是非連續(xù)的物理內(nèi)存空間映射成了連續(xù)的虛擬內(nèi)存空間,不過(guò)這個(gè)已經(jīng)不在我們這篇文章的討論范疇了,我們這里知道我們編程中,我們的變量,代碼其實(shí)都是儲(chǔ)存在這一大塊的連續(xù)的虛擬內(nèi)存空間就夠了。

當(dāng)然,這么一大塊內(nèi)存空間為了能夠被更好地管理,我們通常要對(duì)內(nèi)存進(jìn)行布局,也就是劃分功能塊,我們稱之為 內(nèi)存布局(memory layout) 我們這里以c語(yǔ)言為例。通常我們的劃分是連續(xù)的,如Fig 1所示,通常我們把連續(xù)的虛擬內(nèi)存空間,從低地址位到高地址位,劃分為五大段(segment):

  1. 文本段(test segment)
  2. 初始化后的數(shù)據(jù)段(initialized data segment)
  3. 未初始化的數(shù)據(jù)段(uninitialized data segment)
  4. 棧(stack)
  5. 堆(heap)

我們接下來(lái)分別介紹。

Fig 1. 內(nèi)存布局的示意圖。

文本段

文本段又被稱之為代碼段,其中包含著程序代碼的可被執(zhí)行的指令(CPU中的譯碼器將解釋這些指令,從而實(shí)現(xiàn)數(shù)值計(jì)算或邏輯計(jì)算等)。我們發(fā)現(xiàn)文本段是從最低地址位開(kāi)始分配的,那是因?yàn)?,如果放在堆棧的后面,如果堆棧溢?overflow)了,那么文本段就可能會(huì)被覆蓋掉,造成不可預(yù)料的程序錯(cuò)誤,因此為了避免這個(gè)問(wèn)題,我們把文本段放在了最低位。

通常來(lái)說(shuō),代碼段中的代碼是可以被共享的(感覺(jué)有點(diǎn)像動(dòng)態(tài)鏈接的意思,多個(gè)程序動(dòng)態(tài)鏈接同一個(gè)庫(kù)的程序,而不嘗試去進(jìn)行集成在一起,因?yàn)榧稍谝黄饘?huì)造成多個(gè)同樣指令的多個(gè)副本,造成浪費(fèi)),因此,對(duì)于同一個(gè)模塊(同一個(gè)庫(kù)),我們只需要在文本段保留一個(gè)副本就夠了。文本段通常是只讀的,從而避免程序在意外情況下改變了其中的指令。(如果真的造成了溢出,真的可能會(huì)不可預(yù)料地改變文本段的指令,這個(gè)通常是很危險(xiǎn)的,會(huì)導(dǎo)致這個(gè)系統(tǒng)的崩潰)

初始化后的數(shù)據(jù)段

初始化后的數(shù)據(jù)段(initialized data segment),通常簡(jiǎn)稱為數(shù)據(jù)段(data segment)。數(shù)據(jù)段中儲(chǔ)存的是程序中的全局變量或者是靜態(tài)變量,而這些變量是被程序員初始化過(guò)了的。注意到,數(shù)據(jù)段的數(shù)據(jù)并不意味著只是只讀的,其中的變量可能在程序運(yùn)行中被改變。數(shù)據(jù)段又可以被劃分為初始化過(guò)了的只讀區(qū)(initialized read-only area)和初始化過(guò)了的讀寫區(qū)(initialized read-write area),這個(gè)由程序中的關(guān)鍵字進(jìn)行修飾。舉例而言:

char s[] = "hello world";

如果這個(gè)語(yǔ)句在函數(shù)之外,定義了一個(gè)全局的字符數(shù)組,其儲(chǔ)存在了數(shù)據(jù)段的初始化過(guò)了的讀寫區(qū)。如果像是:

char *string = "hello world";

那么,這個(gè)字符實(shí)體"hello world"將會(huì)被儲(chǔ)存在初始化過(guò)了的只讀區(qū),但是其指針&string本身儲(chǔ)存在了讀寫區(qū)。

未初始化的數(shù)據(jù)段

未初始化的數(shù)據(jù)段(Uninitialized data segment),也被稱之為BSS段,其名字以一個(gè)古老的匯編操作符命名,其代表了“以符號(hào)為始的塊(Block Started by Symbol)”。在程序執(zhí)行之前,在這個(gè)段的數(shù)據(jù)都會(huì)內(nèi)核初始化成0。

未被初始化的這些數(shù)據(jù)從初始化過(guò)的數(shù)據(jù)段(也即是Initialized data segment)的結(jié)尾處開(kāi)始,其中包含著所有的全局變量和靜態(tài)變量,注意到這些變量未曾在代碼中進(jìn)行任何的顯式的初始化。例如:

static int i; // 未經(jīng)過(guò)初始化的靜態(tài)變量,將會(huì)儲(chǔ)存在BSS中
int j; // 定義的全局變量j,其未經(jīng)過(guò)初始化,也是會(huì)儲(chǔ)存在BSS中

棧區(qū)

棧區(qū)(stack)用于儲(chǔ)存自動(dòng)變量,其里面是在函數(shù)每次被調(diào)用的時(shí)候,都會(huì)被保存的一些信息。每次當(dāng)函數(shù)被調(diào)用的時(shí)候,一些信息,例如

  1. 應(yīng)該在何處返回的地址
  2. 調(diào)用者的環(huán)境信息,比如一些寄存器信息等

將會(huì)被儲(chǔ)存在棧區(qū)中(保留現(xiàn)場(chǎng)信息)。這個(gè)被調(diào)用的函數(shù)則會(huì)在棧區(qū)中申請(qǐng)分配內(nèi)存給函數(shù)里面定義的自動(dòng)變量和臨時(shí)變量以供使用。這個(gè)就是為什么在C語(yǔ)言中迭代函數(shù)可以工作的原因了,每次迭代函數(shù)都調(diào)用了其自身的時(shí)候,其會(huì)使用一個(gè)新的棧區(qū)內(nèi)存,因此不同棧區(qū)內(nèi)存之間的內(nèi)容不會(huì)相互干擾,即便他們從源代碼上看起來(lái)的確是同一個(gè)函數(shù),但是他們的實(shí)際內(nèi)存上的內(nèi)容卻得到了隔離。

棧區(qū)(stack)一般是在堆區(qū)(heap)的鄰邊,并且棧區(qū)其數(shù)據(jù)地址的增長(zhǎng)方式和堆區(qū)是相反的,也就是說(shuō)堆區(qū)的數(shù)據(jù)按照初始化的順序,可能是從低地址位到高地址位分配的, 而棧區(qū)的數(shù)據(jù)可能按照 從高地址位到低地址位的方向分配,這種策略減少了數(shù)據(jù)溢出造成的危害。當(dāng)堆區(qū)的指針和棧區(qū)指針相碰時(shí),我們?nèi)菀字溃呀?jīng)沒(méi)有空余的內(nèi)存可以分配了。(在現(xiàn)代大規(guī)模的地址空間和虛擬內(nèi)存技術(shù)的幫助下,棧區(qū)和堆區(qū)可能被安置在任何地方,但是他們一般還是從相反的方向進(jìn)行分配)

棧區(qū)包含著程序棧(program stack),其是一個(gè)LIFO(Last In First Out)的結(jié)構(gòu),一般會(huì)被安置在內(nèi)存的高地址位。在標(biāo)準(zhǔn)的x86結(jié)構(gòu)計(jì)算機(jī)上,它朝著地址0(也就是地址起始點(diǎn))方向增長(zhǎng);然而在其他的一些結(jié)構(gòu)的計(jì)算機(jī)中,它朝著反方向增長(zhǎng)。一個(gè)“棧區(qū)指針”寄存器將會(huì)一直跟蹤著棧區(qū)的頭部(top of the stack),在每次數(shù)據(jù)壓入棧區(qū)的時(shí)候,它將會(huì)自動(dòng)地調(diào)整。為了一個(gè)函數(shù)而壓入棧區(qū)的一系列值,我們稱之為棧幀(stack frame),一個(gè)棧幀至少要包括了返回地址,不然將會(huì)無(wú)法返回被調(diào)用函數(shù),導(dǎo)致出錯(cuò)。

堆區(qū)

堆區(qū)(heap)是用于分配動(dòng)態(tài)內(nèi)存的段。我們用代碼malloc(), realloc(), new等分配的內(nèi)存都儲(chǔ)存在堆區(qū)。堆區(qū)在BSS段的結(jié)尾處開(kāi)始,并且其朝著高地址位的方向增長(zhǎng)。正如我剛才所說(shuō)的,堆區(qū)通過(guò)malloc(),realloc(),free等進(jìn)行管理著內(nèi)存的分配和釋放,其可能會(huì)使用brk或者sbrk系統(tǒng)調(diào)用進(jìn)行調(diào)整其大?。ㄗ⒁獾?code>brk/sbrk的使用和一個(gè)最小堆區(qū)并不足以滿足malloc/realloc/free這些命令功能的完整要求,其也許還需要通過(guò)mmap內(nèi)存映射去潛在地預(yù)定一些非連續(xù)的虛擬內(nèi)存區(qū)域到進(jìn)程的虛擬內(nèi)存空間中)。堆區(qū)是被進(jìn)程中的所有共享庫(kù)和動(dòng)態(tài)加載模組所共享的,比如動(dòng)態(tài)鏈接庫(kù)(.dll, .so)等。

例子

現(xiàn)在有c語(yǔ)言代碼如:

// file name memory-layout.c
#include <stdio.h> 
int main(void) 
{ 
    return 0; 
} 

我們可以通過(guò)指令size對(duì)其使用的各部分的內(nèi)存進(jìn)行報(bào)告,如下所示:

[narendra@CentOS]$ gcc memory-layout.c -o memory-layout
[narendra@CentOS]$ size memory-layout
text       data        bss        dec        hex    filename
960        248          8       1216        4c0    memory-layout

我們?cè)谠瓉?lái)代碼的基礎(chǔ)上添加一個(gè)全局變量,其未曾被初始化:

#include <stdio.h> 
  
int global; /* Uninitialized variable stored in bss*/
  
int main(void) 
{ 
    return 0; 
} 

同樣地我們觀察其內(nèi)存報(bào)告:

[narendra@CentOS]$ gcc memory-layout.c -o memory-layout
[narendra@CentOS]$ size memory-layout
text       data        bss        dec        hex    filename
 960        248         12       1220        4c4    memory-layout

我們發(fā)現(xiàn)BSS區(qū)增大了4個(gè)字節(jié),那個(gè)正是新定義的全局變量的大小。

我們?cè)偬砑右粋€(gè)未曾初始化的靜態(tài)變量試試看:

#include <stdio.h> 
  
int global; /* Uninitialized variable stored in bss*/
  
int main(void) 
{ 
    static int i; /* Uninitialized static variable stored in bss */
    return 0; 
} 

同樣觀察報(bào)告,發(fā)現(xiàn)BSS區(qū)增大到了16.

[narendra@CentOS]$ gcc memory-layout.c -o memory-layout
[narendra@CentOS]$ size memory-layout
text       data        bss        dec        hex    filename
 960        248         16       1224        4c8    memory-layout

如果對(duì)這個(gè)靜態(tài)變量進(jìn)行初始化,那么其多出來(lái)的內(nèi)存將會(huì)在數(shù)據(jù)段中,而不是在BSS段中:

#include <stdio.h> 
  
int global; /* Uninitialized variable stored in bss*/
  
int main(void) 
{ 
    static int i = 100; /* Initialized static variable stored in DS*/
    return 0; 
} 
[narendra@CentOS]$ gcc memory-layout.c -o memory-layout
[narendra@CentOS]$ size memory-layout
text       data        bss        dec        hex    filename
960         252         12       1224        4c8    memory-layout

Reference

[1]. https://www.geeksforgeeks.org/memory-layout-of-c-program/

[2]. https://www.geeksforgeeks.org/common-memory-pointer-related-bug-in-c-programs/

[3]. https://www.tutorialspoint.com/compiler_design/index.htm

聲明:本內(nèi)容為作者獨(dú)立觀點(diǎn),不代表電子星球立場(chǎng)。未經(jīng)允許不得轉(zhuǎn)載。授權(quán)事宜與稿件投訴,請(qǐng)聯(lián)系:editor@netbroad.com
覺(jué)得內(nèi)容不錯(cuò)的朋友,別忘了一鍵三連哦!
贊 6
收藏 5
關(guān)注 52
成為作者 賺取收益
全部留言
0/200
成為第一個(gè)和作者交流的人吧