C語言的內存模型
時間:2024-02-21 來源:華清遠見
1.內存模型
C語言程序中內存大致分為五個區域

1.全局區:bss區(Block Started by Symbol)用來存放程序中未初始化的全局變量的內存區域,data區(data segment)用來存放程序中已初始化的全局變量的內存區域。
2. 常量區: 常量區存放的是常量,如const修飾的全局變量、字符常量、字符串常量以及整型常量等
3. 代碼區(text segment): 用來存放程序執行代碼的內存區域。
4. 堆(heap):用來存放進程運行中被動態分配的內存段,它的大小并、不固定,可動態擴張或縮減,需要程序員手動申請和釋放。當調用malloc分配內存時,新分配的內存就被動態添加到堆上,當調用free釋放堆區申請的內存。
5. 棧(stack):存放程序中的局部變量(但不包括static聲明的變量,static變量放在數據段中)。同時,在函數被調用時,棧用來傳遞參數和返回值。由于棧先進先出特點。所以棧特別方便用來保存/恢復調用現場
2.代碼區
存放 CPU 執行的機器指令。通常代碼區是可共享的(即另外的執行程序可以調用它),使其可共享的目的是對于頻繁被執行的程序,只需要在內存中有一份代碼即可。代碼區通常是只讀的,使其只讀的原因是防止程序意外地修改了它的指令。另外,代碼區還規劃了局部變量的相關信息。
總結:你所寫的所有代碼都會放入到代碼區中,代碼區的特點是共享和只讀。
3.全局區
全局區中主要存放的數據有:全局變量、靜態變量(被static修飾的變量)
全局區也叫靜態區
這部分可以細分為data區和bss區
3.1全局變量
定義在函數體外部的是全局變量,未初始化是初值自動為0。
存儲位置:全局區
生命周期:同整個程序共存亡
作用域:整個程序
3.2 static修飾的變量
1)變量的存放位置在全局區(靜態區)
如果靜態變量有初值,存放.data區,沒有初值存放在.bss區域
2)生命周期為整個程序
3)限制作用域
修飾局部變量: 和普通局部變量作用域沒有區別,但是生命周期被延長為整個程序。
也就是在作用函數外有生命但是不能被操作,變成了植物人。
修飾全局變量: 限制在本文件中使用,不能被extern外部引用
4)只初始化一次,初值賦值0
舉例對比用static和不用的局部變量在函數中自加操作
#include <stdio.h>
void fun()
{
static int a;
int b=0;
a++;
b++;
printf("%4d %4d\n",a,b);
}
int main()
{
fun();
fun();
return 0;
}
第一次調用打印:1 1
第二次調用打印:2 1
因為被static修飾的局部變量只初始化一次,并且會保留上次調用函數結束后的值,因為存放在全局區了,但是作用域不變還是作用域當前函數內。
4. 常量
程序運行中不會發生變化的量,存在常量區。
4.1 字符型常量
類型為char。從ascii表中找的的字符都是字符常量,不可以改變。用單引號括起來的就是字符常量,例如'A'。
用’’括起來就是字符常量:
'a' -字符a
'\0' 空字符
' ' 空字符
'\n' 換行
例子:
printf("%c\n",'A');
printf("%c\n",97);
printf("%c\n",'\x42');
printf("%c\n",ca);
可以把字符常量賦值給字符變量
舉例子:
char a = 'A';
char b = '\x41';
char c = '\101';
printf("%c\n",97);
printf("%c\n",'\x41');
printf("%c\n",ca);
printf("%c\n",'A'+1);
因為C規定轉義字符'\x41'中\是轉義字符引導符,后跟一個x表示x后面的數字是十六進制表示法,用' '括起來表示一字節ASCII碼。\轉義符后面加數字代表轉義成八進制的字符,后面的數字是八進制。
4.2 字符串常量
用" "括起來的是字符串
"hello" 字符串后面\0
printf("%s\n","hello");
4.3 整型常量
整型就是類型為整數的常量,包括從負數到零到正數的所有整數。可以用二進制、八進制、十進制和十六進制表示。
例如打印15三種表達形式:
int a=15;//把整型常量賦給整型變量
printf("%d\n",0b1111);//二進制
printf("%d\n",15);//十進制
printf("%d\n",017);//八進制
printf("%d\n",0xF);//十六進制
printf("%d\n",a);
打印出來都是15
4.4 浮點型常量
浮點型常量就是類型為浮點數的常量,包括從負數到零到正數的所有浮點數。
float double
4.5 指數常量
用科學計數法表示
3*10^8 ->3e8
2*10^-12 ->2e-12
4.6 標識常量(宏定義)
宏定義:起標識作用
(1)只是單純的進行文本替換,在預處理的時候進行。
(2)遵循標識符的命名規則
(3)一般大寫表示
格式:#define 宏名 常量或表達式
特點:只能單純替換,不要進行手動運算(原樣替換,替換完再計算)
5.棧區
棧區用來存儲函數內部的變量(包括main()函數)。它是一個FILO(First In Last Out,先進后出)的數據結構。每當一個函數聲明一個新的變量它將被壓入棧中。當一個函數運行結束后,這個函數所有在棧中相關的變量都將被刪除,而且它們所占用的內存將會被釋放。這就產生了函數內部的局部變量。棧區是一段非常特殊的內存區,它由CPU自動管理,所以你不必手動申請和釋放內存。
內存由系統自動申請,在變量生命周期結束時由系統釋放,也就是說,在程序運行的時候,系統有多個任務,就是檢測變量是否該釋放了,簡單來說,就是cpu要抽時間去執行這部分功能。所以,如果這種變量比較多,不加節制的定義的話,那CPU的額外的工作量就會加大,綜合下來,程序的運行效率就會低下。
舉例:
char *p = "hello";
//就可以說p在棧區開辟4字節空間存放字符串常量“hello”的首地址
//“hello“:存放在常量區
char buf[32]="hello";
//可以說buf:在棧區開辟32字節空間,存放"hello"字符串
5.1局部變量
定義在函數體內部的為局部變量
存儲位置:棧區
生命周期:同函數共存亡
作用域:作用域函數體內部
初值:為初始化時初值未隨機值
5.2如何避免棧的溢出
局部數組過大。當函數內部的數組過大時,有可能導致堆棧溢出。
遞歸調用層次太多。遞歸函數在運行時會執行壓棧操作,當壓棧次數太多時,也會導致堆棧溢出。
指針或數組越界。這種情況最常見,例如進行字符串拷貝,或處理用戶輸入等等。
解決這類問題的辦法有兩個:一是增大棧空間,二是改用動態分配,使用堆(heap)而不是棧(stack)。
6. 堆區
堆區由我們程序員隨時申請,由我們自己隨時釋放。一般可以用malloc()開辟,用free()釋放。
注意:
1)手動開辟堆區空間,要注意內存泄漏:
當指針指向開辟堆區空間后,又對指針重新賦值,則沒有指針指向開辟的堆區空間,就會造成內存泄漏。
2)使用完堆區空間后及時釋放空間
為什么分這么多區域?在實際生活中,這和公司差不多,部門分的細致,工作分發的就有針對性,效率就會高。

