何為變量?
變量一般可以細分為如下圖:
本節重點為了讓大家理解內存模型的“棧”,暫時不考慮“靜態變量” 的情況,并約定如下:
“全局變量”僅僅默認為“普通全局變量”;
“局部變量”僅僅默認為“普通局部變量”。
如何判定全局變量和局部變量?
簡單直觀的來說,全局變量就是在函數外面定義的變量,局部變量就是在函數內部定義的變量,下面的例子能很清晰地說明全局變量和局部變量的判定方法:
unsigned char a;//在函數外面定義的,所以是全局變量。 voidmain()//主函數 { unsigned char b;//在函數內部定義的,所以是局部變量。 b=a; while(1) { } }
全局變量和局部變量的內存模型
單片機內存包括ROM和RAM 兩部分,ROM存儲的是單片機程序中的指令和一些不可更改的常量數據,而 RAM存放的是可以被更改的變量數據;
也就是說,全局變量和局部變量都是存放在RAM,但是,雖然都是存放在 RAM,全局變量和局部變量之間的內存模型還是有明顯的區別的。
因此,分了兩個不同的RAM區,全局變量占用的 RAM區稱為全局數據區, 局部變量占用的 RAM 區稱為棧。
它們的內存模型到底有什么本質的區別呢?
全局數據區就像你自己家的房間,是唯一的,一個房間的地址只能你一個人住(假設你還是單身狗的時候),而且是永久的(sorry),所以說每個全局變量都有唯一對應的 RAM 地址, 不可能重復的。
棧就像客棧, 一年下來每天晚上住的人不一樣,每個人在里面居住的時間是有期限的,不是長久的,一個房間的地址一年下來每天可能住進不同的人,不是唯一的。
全局數據區的全局變量擁有永久產權,棧區的局部變量只能臨時居住在賓館客棧, 地址不是唯一的, 有期限的。
棧是給程序里所有函數內部的局部變量共用的,函數被調用的時候,該函數內部的每個局部變量就會被分配對應到棧的某個RAM 地址,函數調用結束后,該局部變量就失效。
因此它對應的棧 的RAM空間就被收回,以便給下一個被調用的函數的局部變量占用。
舉例借用“賓館客棧”來比喻局部變量所在的“棧”。
voidfunction(void);//子函數的聲明 voidfunction(void)//子函數的定義 { unsignedchara;//局部變量 a=1; } voidmain()//主函數 { function();//子函數的調用 }
我們看到單片機從主函數 main 往下執行, 首先遇到function()子函數的調用, 所以就跳到function()函數的定義那里開始執行, 此時的局部變量 a 開始被分配在 RAM的“棧區” 的某個地址, 相當于你入住賓館被分配到某個房間。
單片機執行完子函數function() 后,局部變量 a 在 RAM 的棧區所分配的地址被收回, 局部變量a 消失,被收回的RAM地址可能會被系統重新分配給其它被調用的函數的局部變量。
此時相當于你離開賓館,從此你跟那個賓館的房間沒有啥關系, 你原來在賓館入住的那個房間會被賓館老板重新分配給其他的客人入住。
全局變量的作用域是永久性不受范圍限制的,而局部變量的作用域就是它所在函數的內部范圍。全局變量的全局數據區是永久的私人房子,局部變量的棧是臨時居住的客棧。
總結如下
每定義一個新的全局變量,就意味著多開銷一個新的RAM 內存。而每定義一個局部變量,只要在函數內部所定義的局部變量總數不超過單片機的棧區,此時的局部變量不開銷新的 RAM內存, 因為局部變量是臨時借用棧的, 使用后就還給棧,棧是公共區, 可以重復利用,可以服務若干個不同的函數內部的局部變量。
單片機每次進入執行函數時,局部變量都會被初始化改變,而全局變量則不會被初始化, 全局變量是一直保存之前最后一次更改的值。
有哪些常見疑問?
全局數據區和棧區是誰在幕后分配的, 怎么分配的?
是C編譯器自動分配的, 至于怎么分配,誰分配多一點,誰分配少一點,C 編譯器會有一個默認的比例分配, 我們一般都不用管。
棧區是臨時借用的,子函數被調用的時候,它內部的局部變量才會“臨時” 被分配到“棧” 區的某個地址,那么問題來了,誰在幕后主持“棧區” 這些分配的工作?
單片機已經上電開始運行程序的時候,編譯器已經不起作用,“棧區” 分配給函數內部局部變量的工作,確實是 C 編譯器做的,但這是在單片機上電前。
C 編譯器就把所有函數內部的局部變量的分配工作就規劃好了,都指定了如果某個函數一旦被調用,該函數內部的哪個局部變量應該分到“棧區” 的哪個地址,C 編譯器都是事先把這些“后事” 都交代完畢了才結束自己的生命。
等單片機上電開始工作的時候,雖然C編譯器此時不在了,但是單片機都是嚴格按照C編譯器交代的遺囑開始工作和分配“棧區”的。因此,“棧區” 的“臨時分配” 非真正嚴格意義上的“臨時分配”。
函數內部所定義的局部變量總數不超過單片機的“棧” 區的 RAM 數量, 那, 萬一超過了“棧” 區的 RAM數量, 后果嚴重嗎?
這種情況專業術語叫爆棧。程序會出現莫名其妙的異常,后果特別嚴重。
為了避免這種情況, 一般在編寫程序的時候, 函數內部都不能定義大數組的局部變量, 局部變量的數量不能定義太多太大,尤其要避免剛才所說的定義開辟大數組局部變量這種情況。
大數組的定義應該定義成全局變量,或者定義成 靜態的局部變量。
有一些C編譯器,遇到“爆棧” 的情況,會好心跟你提醒讓你編譯不過去,但是也有一些 C 編譯器可能就不會給你提醒,所以大家以后做項目寫函數的時候,要對爆棧心存敬畏。
全局變量和局部變量的優先級
剛才說到,全局變量的作用域是永久性并且不受范圍限制的,而局部變量的作用域就是它所在函數的內部范圍。
那么問題來了,假如局部變量和全局變量的名字重名了,此時函數內部執行的變量到底是局部變量還是全局變量?
這個問題就涉及到優先級。
注意,當面對同名的局部變量和全局變量時,函數內部執行的變量是局部變量,也就是局部變量在函數內部要比全局變量的優先級高。
我們來舉一些例子
請看下面第一個例子
unsignedchara=5;//此處第1個a是全局變量 voidmain()//主函數 { unsignedchara=2;//此處第2個a是局部變量,跟上面全局變量的第1個a重名了 print(a);//把a發送到電腦端的串口助手軟件上觀察 while(1) { } }
正確的答案是 2。在函數內部的局部變量比全局變量的優先級更加高。
雖然這里的兩個a重名了, 但是它們的內存模型不一樣,第1個全局變量的a是分配在全局數據區,是具有唯一的地址的,而第2個局部變量的a是被分配在臨時的棧區的,寄生在 main 函數內部。
再看下面第二個例子
voidfunction(void);//函數聲明 unsignedchara=5;//此處第1個a是全局變量 voidfunction(void)//函數定義 { unsigned char a=3;//此處第 2 個 a 是局部變量。 } voidmain()//主函數 { unsigned char a=2;//此處第 3 個 a 也是局部變量。 function();//子函數被調用 print(a);//把 a 發送到電腦端的串口助手軟件上觀察。 while(1) { } }
正確的答案是2。因為,function這個子函數是被調用結束之后,才執行 print(a)的, 就意味函數內部的局部變量(第2個局部變量 a)是在執行 print(a)語句的時候就消亡不存在了, 所以此時print(a)的a是第3個局部變量的a(在 main 函數內部定義的局部變量的 a)。
再看下面第三個例子
voidfunction(void);//函數聲明 unsignedchara=5;//此處第1個a是全局變量 voidfunction(void)//函數定義 { unsignedchara=3;//此處第2個a是局部變量 } voidmain()//主函數 { function();//子函數被調用 print(a);//把a發送到電腦端的串口助手軟件上觀察 while(1) { } }
正確的答案是5。因為function這個子函數是被調用結束之后,才執行print(a)的,就意味function函數內部的局部變量(第2個局部變量)是在執行function(a)語句的時候就消亡不存在了。
同時,因為此時main函數內部也沒有定義a的局部變量,所以此時function(a)的a是必然只能是第1個全局變量的a(在main函數外面定義的全局變量的a)。
最后
看到本文之后,相信大家已經對棧有了一些基礎的認識,在嵌入式編程中,我們也要時刻注意,避免爆棧;如果有錯誤歡迎指出,我們下一期,再見。
-
存儲
+關注
關注
13文章
4328瀏覽量
85942 -
函數
+關注
關注
3文章
4338瀏覽量
62739 -
模型
+關注
關注
1文章
3261瀏覽量
48914
原文標題:從嵌入式編程中感悟「棧」為何方神圣?
文章出處:【微信號:strongerHuang,微信公眾號:strongerHuang】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論