最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

觀察C編譯器對局部變量作用域的約束作用

2022-09-17 16:29 作者:28283844972_bili  | 我要投稿

我們知道,C局部(自動)變量的作用域和生命周期僅限于其自身所在的語句塊,此處所說的語句塊包括函數(shù)代碼塊,由 { } 構(gòu)成的一般代碼塊,至于局部靜態(tài)變量,雖說其生命周期存在于整個程序運(yùn)行期間,但其作用域和局部變量無異,同樣受限于身處的語句塊。

不難理解兩者生命周期的差異,C進(jìn)程中局部(自動)變量被分配在內(nèi)存調(diào)用棧中,隨著程序的運(yùn)行,這片內(nèi)存區(qū)域所管理的內(nèi)容被頻繁地分配和銷毀。例如函數(shù)在被調(diào)用時會自動分配棧幀,離開時棧幀被自動釋放,同時函數(shù)中所定義的局部變量就分配在這些棧幀中,因此當(dāng)函數(shù)退出時棧幀被自動釋放,局部變量所在的內(nèi)存空間也將被回收,容身之所不復(fù)存在,作用域和生命周期也無從談起。當(dāng)然對于C這種對邊界檢查較為寬松的語言,如果熟悉內(nèi)存棧局部的話,短時間內(nèi)通過越界訪問指定位置,也是有可能在局部變量的作用域外進(jìn)行一些操作,當(dāng)然這種行為的結(jié)果是未定義的,取決于編譯器的約束行為。

至于局部靜態(tài)變量,同其他全局變量一樣都是存放在全局?jǐn)?shù)據(jù)區(qū),存在于程序的整個運(yùn)行期間,因此每次進(jìn)入或離開其所在的語句塊并不會重新為其分配內(nèi)存空間,不過受限于編譯器的約束行為,局部靜態(tài)變量自身在作用域以外是不可見的。事實(shí)上,即使是在作用域中操作局部靜態(tài)變量,也是緊緊地圍繞它在全局?jǐn)?shù)據(jù)區(qū)的存儲地址展開的,并不會在調(diào)用棧中新開辟額外的內(nèi)存空間。

為了進(jìn)一步觀察C編譯器對局部變量作用域的約束作用,我們可以從最簡單的語句塊入手——由 { } 構(gòu)成的一般語句塊,通過觀察學(xué)習(xí),我個人認(rèn)為C局部變量,包括局部靜態(tài)變量的作用域,從狹義上來說是一種受編譯器約束的行為;廣義上就是由C編譯器、操作系統(tǒng)、硬件等因素共同作用之下的行為,即使是在函數(shù)構(gòu)成的語句塊內(nèi)也不例外。

在此之前,給出本次實(shí)驗(yàn)環(huán)境,不能保證在不同環(huán)境下最終結(jié)果的一致性,特別是涉及在作用域外訪問局部變量的行為,取決于平臺軟硬件和C編譯器:

平臺軟硬件信息:

GCC編譯器信息:

objdump反匯編工具信息:

首先我們用一段非常簡單的代碼演示來驗(yàn)證作用域約束行為的存在,如果你的編輯器足夠智能的話,在編寫階段就會得到錯誤提示“identifier "xxx" is undefined”,可惜我的Vim沒有那么智能,不會給予錯誤提示,妹說那就是一遍過,照常編譯并觀察報錯信息。

GCC非常貼心地貼出了編譯錯誤地信息,快速定位問題所在,原因是使用了未定義的符號,進(jìn)一步分析可知,想要正常訪問作用域外的局部變量,僅在程序編譯的環(huán)節(jié)就被C編譯器阻止了,編譯器對這些局部變量的作用域給出了約束,在作用域外是不可見狀態(tài),因此自然無法訪問。這是非常重要的錯誤提示,可以避免大部分情況下意外操作作用域之外的內(nèi)存空間導(dǎo)致的各種未定義行為。

現(xiàn)在我們驗(yàn)證了作用域約束行為的確存在,另外一個常識是當(dāng)我們在語句塊之前定義一個同名變量,進(jìn)入語句塊嘗試在局部變量之后操作這個同名變量,則會出現(xiàn)局部變量暫時覆蓋同名變量可見狀態(tài)的行為,表現(xiàn)為所有操作將作用于局部變量,直到離開語句塊之后原先的同名變量重新對用戶可見,如下所示,編譯后觀察變量值變化:

結(jié)果符合預(yù)期,盡管語句塊之前的同名變量作用域是能夠延伸至語句塊內(nèi)部,不過當(dāng)遇到重名局部變量之后作用域臨時被覆蓋,上述行為同樣受到編譯器的約束。

讓我們剔除一些額外的代碼,邀請局部靜態(tài)變量一同參加接下來的匯編盛宴,場地布局(源碼)如下:

活動場地都準(zhǔn)備好之后,讓我們看看客人是如何入場的吧?使用GCC僅編譯源碼,不進(jìn)行匯編、鏈接,gcc -S test.c

沒怎么學(xué)習(xí)過匯編語言,看著信息量有點(diǎn)大,剔除些說明信息會不會好一些呢?

這樣看上去就好多了,憑感覺刪去了一些貌似和話題無關(guān)的信息,粗略的觀察一下匯編代碼,大致也能猜到?movl $123, -4(%rbp) 和 main 代碼塊中的 i_local 變量有關(guān),下一條 movl $456, -8(%rbp) 指令和 { } 代碼塊中的 i_local 局部變量有關(guān),僅在最下方的描述信息中找到有關(guān)局部靜態(tài)變量的蹤跡,其他的大概率和 main 函數(shù)棧幀的分配、釋放有關(guān),加上一些信息描述。

結(jié)合理論知識分析可知,局部靜態(tài)變量無需像自動變量那樣直到運(yùn)行時才分配內(nèi)存空間,只需要在編譯時記錄類型、大小、偏移量等信息,變量值存放在全局?jǐn)?shù)據(jù)段,偏移量放在全局偏移表(GOT)中便于訪問。而局部(自動)變量需要借助調(diào)用棧進(jìn)行空間分配和釋放,但不同于函數(shù)棧幀中的局部變量,在我的這個例子中,僅有 { } 構(gòu)成的語句塊在離開其作用域后并沒有釋放和回收空間,緊接著的下一條匯編指令是?movl $0, %eax ,顯然這是為 return 0;?語句保存返回值0做準(zhǔn)備,這也就意味著在這個例子中,離開局部變量作用域之后我們依然有辦法可以訪問、操作這片區(qū)域。

不過在此之前,我們還是來完整的觀察一下該如何訪問、操作局部靜態(tài)變量,在原有代碼的基礎(chǔ)上,我們在語句塊內(nèi)增加一行,類似這樣,再次觀察編譯之后的匯編代碼:

前后發(fā)生變化的部分如下, i_static_local++; 被分成了4條匯編指令,無論是獲取局部靜態(tài)變量值還是向存儲空間內(nèi)寫入運(yùn)算結(jié)果,是通過x64模式下RIP相對尋址實(shí)現(xiàn)的,也就是說在作用域以外,依舊是可以通過這種相對尋址訪問局部靜態(tài)變量,只不過編譯器沒有提供、也不會提供相對應(yīng)的操作指令,因?yàn)橐?guī)矩就是規(guī)矩。

為了看清?i_static_local.0 標(biāo)簽的真面目,將上述編譯好的可執(zhí)行文件反匯編得到可執(zhí)行段匯編代碼,找到 main?符號所在部分,此時所有的標(biāo)簽都已經(jīng)被替換成地址偏移值了,且隨著指令地址的改變而變化,不過最終都指向 0x0000000000004028?(本身也是偏移值,套娃了屬于是),可以用十六進(jìn)制計算器手工模擬RIP相對尋址檢驗(yàn)一下。

既然RIP相對尋址在作用域以外的地方不起作用,還有什么辦法可以“借用”一下局部靜態(tài)變量呢?既然生命周期那么長,又不必?fù)?dān)心地址失效的問題,取址,簡單粗暴??,間接地拓展作用域。

我已出倉,感覺良好??

回到局部(自動)變量的例子中來,貌似在短時間內(nèi)同樣也是可以使用語句塊之外的指針來“拓展”作用域,只要我們還未從 main 函數(shù)中返回,整個棧幀空間未被回收。確實(shí)可以做到,不過這次我們嘗試另外一種辦法,在C語言中嵌入?yún)R編代碼,在作用域外模仿對局部變量的讀寫操作。第一次接觸C和匯編的聯(lián)動場面,照貓畫虎地寫了一個樣例:

因?yàn)檫@個例子中的調(diào)用棧布局還算簡單,通過模仿正常情況下,語句塊中局部變量訪問的匯編代碼,嘗試在作用域之外實(shí)現(xiàn)對局部變量的取址和賦值操作,并跳轉(zhuǎn)回原作用域驗(yàn)證,輸出如下:

不難看出,作用域的約束行為確實(shí)存在,但在一定條件下,我們又可以通過某些C編譯器認(rèn)為合法的操作去突破這樣的約束,這一切得益于C語言信任程序員,而且不檢測越界程序運(yùn)行更快的哲學(xué)?以及編譯器相對寬松的檢查機(jī)制?

舉以上例子并不是要想盡辦法打破這種“桎梏”,然后往大坑里面跳,相反地,我們既要嚴(yán)格遵守C編譯器的各種約束行為,也不能完全依賴于C編譯器的約束檢查,要以更加謹(jǐn)慎的約束要求審視自己的代碼。所以從狹義上來說,我個人以為符號作用域是一種受C編譯器的約束行為,是一種軟件層面的約束,是一種較為寬松的約束行為。不過這并不會妨礙我喜歡C??

寫在最后:

注意,注意,注意,重要的事情說3遍!首先我個人才學(xué)淺薄,暫時還在嘗試摸到C的入門門檻,寫代碼的格式還不夠規(guī)范,更別說之前幾乎沒怎么接觸過匯編,肯定會有不少紕漏和謬誤,望各位道行高深的大佬輕噴,歡迎在評論區(qū)或私信中友好交流和指正??

其次,我不能保證遵照上述操作流程,在不同平臺下都能復(fù)現(xiàn)出相同的結(jié)果,這取決于你的平臺軟硬件環(huán)境,或許有些編譯器的行為約定即使是普通語句塊在執(zhí)行結(jié)束后,其中的局部變量需要立即釋放和回收,其行為結(jié)果也是未定義的。我也不建議通過某些“耍小聰明”的方式刻意去破壞局部變量作用域的約束,如果你不想徒增某些莫名其妙的Bug的話??

最后,專欄僅作為入門學(xué)習(xí)使用,實(shí)驗(yàn)結(jié)果和文章觀點(diǎn)自行甄別,附上所有的參考資料。

參考資料:

  • https://www.bilibili.com/video/BV1at411j7vrC代碼中嵌入?yún)R編代碼的寫法

  • https://blog.csdn.net/littlehedgehog/article/details/2259665GCC內(nèi)嵌匯編

  • https://www.cnblogs.com/whutzhou/articles/2638498.htmlGCC嵌入?yún)R編代碼

  • https://blog.csdn.net/pizi0475/article/details/6301660更多有關(guān)C代碼嵌入?yún)R編

  • https://www.cnblogs.com/sky-heaven/p/7561625.html同上

  • https://www.zhihu.com/question/270485830另外來說說x86-64的rip相對數(shù)據(jù)尋址

  • https://blog.csdn.net/hit_shaoqi/article/details/108063166匯編:靜態(tài)變量與RIP

  • https://www.polarxiong.com/archives/x64%E4%B8%8BPIC%E7%9A%84%E6%96%B0%E5%AF%BB%E5%9D%80%E6%96%B9%E5%BC%8F-RIP%E7%9B%B8%E5%AF%B9%E5%AF%BB%E5%9D%80.htmlx64下PIC的新尋址方式:RIP相對尋址

  • https://blog.csdn.net/qq_43401808/article/details/86476526Intel 64/x86_64/x86/IA-32處理器的指令指針(IP/EIP/RIP)



觀察C編譯器對局部變量作用域的約束作用的評論 (共 條)

分享到微博請遵守國家法律
信丰县| 巴林右旗| 微山县| 襄垣县| 平罗县| 哈巴河县| 凤翔县| 乐东| 应用必备| 仙游县| 南岸区| 黄石市| 应城市| 江永县| 兴义市| 安阳市| 枝江市| 迁西县| 紫金县| 巨野县| 武夷山市| 沂源县| 台安县| 鹤岗市| 义乌市| 河北区| 朝阳区| 洪湖市| 寻甸| 禹州市| 肃宁县| 普陀区| 红河县| 武宁县| 寿光市| 襄汾县| 德令哈市| 昭平县| 卓资县| 丹江口市| 睢宁县|