#include <stdio.h>
const int a = 10; //全局常量a int main(void) { const int b = 20; //局部常量b int* pa = (int*)&a; int* pb = (int*)&b; *pa = 30; //可以嗎?能成功賦值嗎? *pb = 30; //可以嗎?能成功賦值嗎? return 0; }
作為編程新手的我,有時候寫程序難免會有種迷糊的感覺,雖然寫着代碼,但總覺得哪里不自在不通透...像上面的代碼,我第一次看到的時候根本沒有自信回答出來,我覺着應該有不少編程新人和我一樣吧>o<
先從(Linux平台下)虛擬內存管理說起,
寫C程序時,我們經常會打印一個指針地址,說這個指針指向某某內存地址.可這些地址是真實物理內存地址嗎?不是!這些只是虛擬內存地址.
當一個C程序調入內存開始執行后,在內存中就會產生一個進程.而在多任務操作系統中每個進程都擁有一片屬於自己的內存空間(內存沙盤),這個沙盤就是虛擬地址空間,在32位下是一個4GB的大小的地址塊,這些虛擬地址通過頁表映射到物理內存.
但系統並不會真的一下分配給每一個進程4GB的物理內存空間的映射= =(不現實啊),這4GB只能是說邏輯地址,它會隨着進程的真實需要自動擴展映射到物理內存空間,最大到4GB.
4GB(地址0-0xFFFFFFFF)其中1GB必須保留給系統內核(這是Linux平台下),也就是說進程自身只能擁有3GB的地址(0-0xC0000000),如圖
我自己畫的= =:
代碼區:程序(函數)代碼所在,由編譯而得到的二進制代碼被載入至此.代碼區是只讀的!有執行權限.代碼區一般都從0x08048000地址開始(linux下).值得注意的是,字符串字面值(如"Hello World")就存儲在這個區.
數據段和BSS段:合稱靜態區(全局區),用來存儲靜態(全局)變量.區別是 前者(數據段)存儲的是已初始化的靜態(全局)變量,可讀寫.
后者(BSS段)存儲的是未初始化的靜態(全局)變量,可讀寫.
堆:自由存儲區.不像全局變量和局部變量的生命周期被嚴格定義,堆區的內存分配和釋放是由程序員所控制的.申請方式:C中是malloc函數,C++中是new標識符.
棧:由系統自動分配和釋放.存儲局部(自動)變量. 一般說的堆棧,其實是指 棧!
另外,值得注意的是,堆是由低地址向高地址分配空間;棧卻是由高地址向低地址分配空間.
下面這段代碼進一步說明C程序中各數據的內存布局:
#include <stdio.h> #include <stdlib.h> int i1 = 10; //靜態全局區(data段) int i2; //靜態全局區(bss段) static int i3 = 30; //靜態全局區(data段) const int i4 = 40; //代碼區!!! void fun(int i5) //棧區 { int i6 = 60; //棧區 static int i7 = 70; //靜態全局區(data段) const int i8 = 80; //棧區!!! char* str1 = "ABCDE"; //代碼區(字符串常量) char str2[] = "ABCDE"; //棧區(字符數組) int* pi = malloc(sizeof(int)); //堆區 printf("&i5=%p\n", &i5); printf("&i6=%p\n", &i6); printf("&i7=%p\n", &i7); printf("&i8=%p\n", &i8); printf("str1=%p\n", str1); printf("str2=%p\n", str2); printf("pi=%p\n", pi); free(pi); } int main(void) { printf("&i1=%p\n", &i1); printf("&i2=%p\n", &i2); printf("&i3=%p\n", &i3); printf("&i4=%p\n", &i4); fun(50); return 0; }
(gcc編譯)程序輸出:
至此,從地址大小比較可以看出
1)靜態(static)全局變量 和 靜態(static)局部變量 都在 靜態全局區.
2)全局常量i4保存在代碼區,而局部常量i8保存在棧區.
所以最上面的問題是,代碼區只讀,修改全局常量會引發運行時段錯誤,而局部常量是可以成功賦值修改的.
3)字符串字面值在代碼區(所以不可修改),但是字符指針str1在棧區;字符數組str2在棧區(所以可以修改).
最后要說,上述是Linux下的虛擬內存布局順序,Windows下會有所不同.但是對於C程序中數據應該存儲在哪里都是一致的.
本文相關參考: 剖析程序的內存布局 (建議閱讀)