C语言中递归什么时候能够省略return引发的思考:通过内联汇编解读C语言函数return的本质...
- 事情的經(jīng)過是這種,博主在用C寫一個簡單的業(yè)務(wù)時使用遞歸,因為粗心而忘了寫return。結(jié)果發(fā)現(xiàn)返回的結(jié)果依舊是正確的。經(jīng)過半小時的反匯編調(diào)試。證明了我的猜想,如今在博客里分享。也是對C語言編譯原理的一次加深理解。
- 引子:
- 首先我想以一道題目引例,比較能體現(xiàn)出問題。
- 問題是。運行如上的程序,打印出來的數(shù)值是多少?
- 大家可能會覺得這個非常的弱智,即使作為小公司的筆試題來說都登不上大雅之堂。
——————————–圖1 例題1的運行結(jié)果——————— - 答案是2,毫無疑問,僅僅是一個簡單的遞歸而已。
可是假設(shè)我把題目改一下
- 大家看看上邊的程序。運行結(jié)果會是多少?
可能有非常多朋友細(xì)心已經(jīng)發(fā)現(xiàn)了貓膩。
可能也有部分朋友會有些困惑,這個程序僅僅是在遞歸的實現(xiàn)函數(shù)后中加了一個無關(guān)緊要的函數(shù)調(diào)用,為什么會影響函數(shù)返回的結(jié)果呢。
其實printf打印出來的結(jié)果不對。運行結(jié)果是3
—————————-圖2 例題2的運行結(jié)果————————- - 為什么會出現(xiàn)這個問題呢。實際上正常情況下的遞歸。
在else語句里進(jìn)行遞歸調(diào)用時。應(yīng)當(dāng)加上return。
因為return的缺失,導(dǎo)致了函數(shù)返回值被changestack()函數(shù)篡改。從而在main函數(shù)中讀到了錯誤的返回值。
- 假設(shè)將上文的代碼改正如上,那不會出現(xiàn)不論什么問題。
(當(dāng)然不會出錯,此時有了return,return后邊的changestack根本就不會有不論什么機(jī)會運行)
如今來一步一步來分析發(fā)生錯誤的本質(zhì)。 ——————–圖三 例二函數(shù)的遞歸分析—————————
我們分析上邊代碼的運行過程。首先在main函數(shù)中調(diào)用Add_Recursion(1,1),本意就是計算1+1的值,而且將函數(shù)返回值傳遞給printf打印出來。
在遞歸調(diào)用Add_Recursion函數(shù)(簡稱add)計算1+1時,前兩次遞歸調(diào)用因為不滿足遞歸出口條件(進(jìn)位加數(shù)carry_num為0)。會跳入else分支進(jìn)行遞歸調(diào)用。直到第三次遞歸調(diào)用時因為carry_num為0。這時返回了累加結(jié)果。
- 問題是僅僅有第三次的add遞歸調(diào)用進(jìn)行了return,第一次和第二次在函數(shù)返回時,都沒有return,而是在返回子層次遞歸后調(diào)用changestack()函數(shù)后返回調(diào)用自己的函數(shù)層級。
在第一層遞歸調(diào)用返回給main的時候,add_recursion并沒有return,而是在運行完changestack直接返回main函數(shù),而此時main函數(shù)的printf在解析返回值時,實際上錯誤的解析了changestack的返回值。
因此才出現(xiàn)1+1=3的錯誤
- 綜上分析發(fā)生這一切的原因,就是:
函數(shù)運行結(jié)束返回時。會將返回值壓棧(理論上如此,實際上編譯器會優(yōu)化,將返回值給eax寄存器過渡。VC就是使用的eax臨時保存)。VC編譯器解析函數(shù)返回值(整型)時,直接將eax的值讀出當(dāng)做返回值。
———————-圖四 反匯編分析VC編譯器對return的處理———- - 依據(jù)反匯編分析能夠看到,VC編譯器對changestack()中的return 3匯編的結(jié)果,也就是 mov eax,3。實際上就是把返回值賦予eax,由eax寄存器過渡給此函數(shù)的調(diào)用函數(shù)使用。
我們在下圖中能夠看到main函數(shù)中將changestack()的返回值給num賦值的詳細(xì)過程,也就是將eax的值返回給num的所在的內(nèi)存地址。
——————————圖五 函數(shù)返回值的“彈棧”細(xì)則——————————-這樣一切就有了解釋。
——————-圖六 例題一為什么會碰巧正確的遞歸分析—————
- 盡管第一題的結(jié)果盡管正確,printf在讀取Add_Recursion返回值時。讀取的不是第一次遞歸調(diào)用的結(jié)果,而是第三次遞歸調(diào)用return b的結(jié)果(第三次遞歸返回時,暫存在eax寄存器中)。而在之后的遞歸返回中,湊巧eax都沒有被改變。
因此這樣使用遞歸(盡管沒有在須要return的地方return)是能夠得到正確結(jié)果。
實際上我們能夠用一條內(nèi)聯(lián)匯編代碼驗證我們的猜想是否正確。我們在遞歸調(diào)用的后邊,使用內(nèi)聯(lián)匯編加上一條匯編代碼改變eax的值。
——————————-圖七 用內(nèi)聯(lián)匯編解讀C語言的return本質(zhì)—————————–
我們在遞歸函數(shù)Add_Recursion的后邊加了一條匯編代碼,讓函數(shù)結(jié)束時改變eax的值。能夠看到。主函數(shù)中,將函數(shù)返回值誤覺得了我們在匯編語言中設(shè)定的3.打印出了1+1=3這種謬論。
實際上,我們在編譯例題中的程序在編譯時C編譯器會提出警告
warning C4715: “Add_Recursion”: 不是全部的控件路徑都返回值
有返回值的函數(shù),不是全部的支路都會進(jìn)行返回值,假設(shè)大家把博客中的程序在更加嚴(yán)格的C++編譯器上編譯會報錯。這僅僅是一個非常easy的案例。或許我們會運氣好實現(xiàn)函數(shù)的功能,可是在進(jìn)行復(fù)雜情況的樹狀甚至圖狀遞歸中,假設(shè)不確定自己是否一定能得到終于結(jié)果,請務(wù)必將每一種情況都return返回值,這樣來避免程序意外出錯。
C語言的靈活性應(yīng)該給我們造福,而不應(yīng)該給我們的程序提供不穩(wěn)定的因素。
轉(zhuǎn)載于:https://www.cnblogs.com/mthoutai/p/7300489.html
總結(jié)
以上是生活随笔為你收集整理的C语言中递归什么时候能够省略return引发的思考:通过内联汇编解读C语言函数return的本质...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: oracle分区
- 下一篇: 有了BBdoc文档搜索,就不要使用Doc