99久久全国免费观看_国产一区二区三区四区五区VM_久久www人成免费看片中文_国产高清在线a视频大全_深夜福利www_日韩一级成人av

徐土豆
認證:優質創作者
所在專題目錄 查看專題
[C語言朝花夕拾] C語言中的命令行輸入參數判斷
用“位操作”取代“取模操作”判斷奇數偶數
c語言運行時出現segment fault的原因
一文理解C語言中的volatile修飾符
C語言中的內存布局(memory layout)
do{}while(false)結構的妙用
作者動態 更多
給定計算預算下的最佳LLM模型尺寸與預訓練數據量分配
3星期前
大模型推理時的尺度擴展定律
3星期前
世界多胞體與世界模型
05-13 09:42
獎勵模型中的尺度擴展定律和獎勵劫持
05-12 08:41
MeCo——給預訓練數據增加源信息,就能減少33%的訓練量并且提升效果
05-08 09:13

C語言中的內存布局(memory layout)

本文轉自徐飛翔的“C語言中的內存布局(memory layout)

版權聲明:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。

內存布局

根據經典的計算機馮洛伊曼模型,內存儲存著計算過程中的代碼和數據等。一般來說,內存是稱之為DRAM,其數據是掉電易失的,我們為了簡化編程過程,通常會把內存空間當作是連續的一大塊,也就是說如果給每個內存小塊進行編址的話,可以從0直接編碼到最大的內存空間上限,我們通常把這個一大塊連續的內存空間稱之為虛擬內存空間,為什么稱之為“虛擬”呢?那是因為物理硬件上的內存上不一定是連續的,其通過了一系列的映射才把可能是非連續的物理內存空間映射成了連續的虛擬內存空間,不過這個已經不在我們這篇文章的討論范疇了,我們這里知道我們編程中,我們的變量,代碼其實都是儲存在這一大塊的連續的虛擬內存空間就夠了。

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

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

我們接下來分別介紹。

Fig 1. 內存布局的示意圖。

文本段

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

通常來說,代碼段中的代碼是可以被共享的(感覺有點像動態鏈接的意思,多個程序動態鏈接同一個庫的程序,而不嘗試去進行集成在一起,因為集成在一起將會造成多個同樣指令的多個副本,造成浪費),因此,對于同一個模塊(同一個庫),我們只需要在文本段保留一個副本就夠了。文本段通常是只讀的,從而避免程序在意外情況下改變了其中的指令。(如果真的造成了溢出,真的可能會不可預料地改變文本段的指令,這個通常是很危險的,會導致這個系統的崩潰)

初始化后的數據段

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

char s[] = "hello world";

如果這個語句在函數之外,定義了一個全局的字符數組,其儲存在了數據段的初始化過了的讀寫區。如果像是:

char *string = "hello world";

那么,這個字符實體"hello world"將會被儲存在初始化過了的只讀區,但是其指針&string本身儲存在了讀寫區。

未初始化的數據段

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

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

static int i; // 未經過初始化的靜態變量,將會儲存在BSS中
int j; // 定義的全局變量j,其未經過初始化,也是會儲存在BSS中

棧區

棧區(stack)用于儲存自動變量,其里面是在函數每次被調用的時候,都會被保存的一些信息。每次當函數被調用的時候,一些信息,例如

  1. 應該在何處返回的地址
  2. 調用者的環境信息,比如一些寄存器信息等

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

棧區(stack)一般是在堆區(heap)的鄰邊,并且棧區其數據地址的增長方式和堆區是相反的,也就是說堆區的數據按照初始化的順序,可能是從低地址位到高地址位分配的, 而棧區的數據可能按照 從高地址位到低地址位的方向分配,這種策略減少了數據溢出造成的危害。當堆區的指針和棧區指針相碰時,我們容易知道,已經沒有空余的內存可以分配了。(在現代大規模的地址空間和虛擬內存技術的幫助下,棧區和堆區可能被安置在任何地方,但是他們一般還是從相反的方向進行分配)

棧區包含著程序棧(program stack),其是一個LIFO(Last In First Out)的結構,一般會被安置在內存的高地址位。在標準的x86結構計算機上,它朝著地址0(也就是地址起始點)方向增長;然而在其他的一些結構的計算機中,它朝著反方向增長。一個“棧區指針”寄存器將會一直跟蹤著棧區的頭部(top of the stack),在每次數據壓入棧區的時候,它將會自動地調整。為了一個函數而壓入棧區的一系列值,我們稱之為棧幀(stack frame),一個棧幀至少要包括了返回地址,不然將會無法返回被調用函數,導致出錯。

堆區

堆區(heap)是用于分配動態內存的段。我們用代碼malloc(), realloc(), new等分配的內存都儲存在堆區。堆區在BSS段的結尾處開始,并且其朝著高地址位的方向增長。正如我剛才所說的,堆區通過malloc(),realloc(),free等進行管理著內存的分配和釋放,其可能會使用brk或者sbrk系統調用進行調整其大小(注意到brk/sbrk的使用和一個最小堆區并不足以滿足malloc/realloc/free這些命令功能的完整要求,其也許還需要通過mmap內存映射去潛在地預定一些非連續的虛擬內存區域到進程的虛擬內存空間中)。堆區是被進程中的所有共享庫和動態加載模組所共享的,比如動態鏈接庫(.dll, .so)等。

例子

現在有c語言代碼如:

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

我們可以通過指令size對其使用的各部分的內存進行報告,如下所示:

[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

我們在原來代碼的基礎上添加一個全局變量,其未曾被初始化:

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

同樣地我們觀察其內存報告:

[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

我們發現BSS區增大了4個字節,那個正是新定義的全局變量的大小。

我們再添加一個未曾初始化的靜態變量試試看:

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

同樣觀察報告,發現BSS區增大到了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

如果對這個靜態變量進行初始化,那么其多出來的內存將會在數據段中,而不是在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

聲明:本內容為作者獨立觀點,不代表電子星球立場。未經允許不得轉載。授權事宜與稿件投訴,請聯系:editor@netbroad.com
覺得內容不錯的朋友,別忘了一鍵三連哦!
贊 6
收藏 5
關注 52
成為作者 賺取收益
全部留言
0/200
成為第一個和作者交流的人吧
主站蜘蛛池模板: caoporn最新 | 三级免费久久无码 | 久久精品国产欧美 | 女人黄色毛片 | 久久线视频| 中文字幕久久久久一区 | 操大爷影院 | 99精品视频69V精品视频 | 中文字幕一区二区三区乱码在线 | 欧美成人精品a片免费区网站 | 国产欧美一区二区精品仙草咪 | 男人天堂免费视频 | 性欧美一区二区 | 久久久亚洲精品一区二区三区 | chaopeng视频 | 一二三四中文在线 | 亚洲日韩在线中文字幕线路2区 | 十八禁视频网站在线观看 | 无码爆乳护士让我爽 | 99久久精品免费看国产 | 最新看片国产精品免费在线 | 久久久久久国产精品MV | 国产人妻精品区一区二区 | 久久久久久国产精品免费播放 | 狠狠五月 | 黄色毛片大全 | 天天做天天爱天天爽综合网 | 精品少妇久久 | 亚洲成人免费视频在线 | 国产黑色丝袜在线视频 | 国产成人一区二区在线观看 | 亚洲美女久久 | 高中生高潮抽搐喷出白浆视频 | 色94色欧美setu | 精品人伦一区二区三区蜜桃视频 | 午夜一级 | 日本成人影院 | 国产精品91xxx | 久久精品人人爽人人爽 | 麻豆疯狂做受xxxx高潮视频 | 2019天天干天天操 |