在正式開始今天的博文之前,先看一段代碼,思考這段代碼有可能出現的情況:
int main() { int j, b[10]; for(j = 1; j <= 10; ++j){ b[j] = 0; } }
看完這段代碼后,如果覺得這段代碼沒有什么坑,那不妨注意一下j的范圍是從1-10,而b[10]會導致b數組越界哦。思考到結果了嗎?先來看看一些內存知識吧。
我們在C/C++中學的變量分三種:局部變量、全局變量(靜態變量和全局變量)、堆變量
局部變量 由程序員定義普通變量時編譯器在內存中的棧空間為其分配一段內存,如:
int b[10], j;
全局變量 由編譯器在內存中的靜態存儲區分配空間,如:
int x, y; // 全局
int main(){
static int m, n; // 靜態
}
堆變量 由程序員用malloc或new在堆上申請一段內存空間,如:
int *a = new int[10], *i = new int; // 動態內存分配
可見編譯器通過將內存邏輯划分為不同區段來提供程序員訪問內存的權限,而每段空間都有各自的大小,一旦程序員在使用過程中將該大小哦耗盡,就會出現內存不足錯誤,比如典型的Stack OverFlow
了解了變量的存儲后,再來思考一個問題,當我在同一個變量區定義幾個不同變量時,他們在內存中是如何排列的?
比如我定義兩個局部變量
int a,b;
那么到底是編譯器先為a分配地址,還是先為b地址分配?(這還用思考嗎?肯定是先定義的先分配啦)分配地址后,到底是a的地址高於b的地址,還是b的地址高於a的地址?也就是下面這兩張圖哪張圖是正確的?
想知道答案只需要將這兩個的地址分別打印出來查看一下即可。這里小編就不打印了,建議讀者自行打印。
打印結果是a的地址高於b的地址,也就是說左圖正確。
但是注意這里的a,b均是局部變量,也就是變量存儲在棧區,根據結果我們發現在棧區定義的變量是先定義的變量分配在高地址區域,后定義的變量被分配在低地址區域(左圖)。
那如果是堆區和靜態存儲區呢?
static int a, b;
int *a = new int, *b = new int;
自己動手打印一下地址吧。
結果發現,堆區和靜態存儲區與棧區恰好相反,堆區/靜態存儲區的變量,都是先定義的變量分配在低地址區域,后定義的變量分配在高地址區域(右圖)。
總結來說,就是局部變量高存低用,全局/靜態/堆變量低存低用。
(高存(低存)描述的是多個變量同時定義時地址的占用方式是從高(低)地址往低(高)地址占)
(低用怎么理解:每個變量在分配了內存單元后都是從該變量所占單元的低地址字節到高地址字節存放/讀取數據)
現在返回來再看上面這段代碼:
int j, b[10];
兩個局部變量,還記得高存低用嗎?自己動手畫一下他們的內存分布圖吧。
清楚了j 和 b數組的內存分配后,再來看這段代碼(上圖中a數組其實為b數組,畫圖的時候忘記改過來了)
int main() { int j, b[10]; for(j = 1; j <= 10; ++j){ b[j] = 0; } }
當 j 值為 10 時,b[10]會導致數組越界,而越界導致的結果就是b[10]會再繼續往高地址占用一個int(4個字節)的存儲空間,很明顯,b[10]占用了 j 的地址,此時b[10] = 0 等價於 j = 0,會導致死循環。
(這里說的是理論結果,實際操作時 b 數組再越界1-2個內存單元才會占到 j 所在地址,實際操作只需要將 j <= 10的條件變為 j <= 11 或 j <= 12即可)
而如果將 j 和 b[10]的定義順序交換一下:
int b[10], j;
就不會出現死循環哦,自己解釋一下原因吧。
這里我們只討論了局部變量的高存低用,別忘了還有全局/靜態/堆變量的低存低用呢。
我們將局部變量的 j 和 b[10]重新定義,從堆上申請內存,也就是
int *j = new int, *b = new int[10];
再遍歷一遍,就不會出現死循環了哦
int main() { int *j = new int, *b = new int[10]; for(*j = 1; *j <= 10; ++*j){ b[*j] = 0; }
}
此時j, b都在堆上,且 j 相對 b 位於低地址處,無論 b 怎么越界都只會繼續占用高址空間,占不到 j 的地址。(定義順序換一下就死循環了哦)
下面附一下我的測試代碼
#include <iostream> using namespace std; int x, y; int main() { cout << "Global variable:\n"; cout << "&x: " << &x << " &y: " << &y << endl; static int m, n; cout << "Static variable:\n"; cout << "&m: " << &m << " &n: " << &n << endl; cout << "Heap variable:\n"; int* a = new int[10], *i = new int; //int i, a[10]; for(*i = 1; *i <= 11; ++*i){ a[*i] = 0; } for(*i = 0; *i <=11; ++*i){ cout << "&a[" << *i << "]:" << &a[*i] << endl; } cout << "&i:" << i << endl; delete [] a; //delete i; //這里由於a的越界占到了i的空間,delete [] a其實已經將i占用的空間釋放,這里再釋放一次就會報錯invaild pointer cout << "Stack variable:\n"; int b[10], j; for(j = 1; j <= 11; ++j){ b[j] = 0; } for(j = 0; j <=11; ++j){ cout << "&b[" << j << "]:" << &b[j] << endl; } cout << "&j:" << &j << endl; }