網站首頁 編程語言 正文
?一、C函數(shù)棧幀開辟以及回退過程
__cdecl(C語言默認調用方式,函數(shù)參數(shù)8字節(jié)以內,使用push。本節(jié)采用此方式)
main函數(shù)的棧幀調用sum函數(shù)的棧幀,sum函數(shù)棧幀使用完了以后回退都是怎么進行的,要搞清楚這個問題必須得看匯編代碼,匯編代碼分為兩種:inter x86(windows)和AT&T(unix)。這兩種匯編非常相似,x86的匯編是從右向左看,unix的匯編是從左向右看的。
局部變量都是通過棧底指針ebp偏移訪問,不生成符號,不屬于數(shù)據,屬于指令。
形參壓棧在C/C++中是從右向左壓棧,因為要支持可變長參數(shù),如果從左向右,編譯器就不知道用戶傳入了多少實參,形參內存是在調用函數(shù)棧幀中開辟,每壓棧一個實參,都會開辟一個形參的空間,棧頂指針esp都會減4字節(jié)。
實參壓棧完成后需要調用call指令來執(zhí)行sum函數(shù),執(zhí)行完sum函數(shù)后,執(zhí)行完sum函數(shù)后需要回到調用指令(call)的下一條指令繼續(xù)執(zhí)行。
call指令做兩件事:
- 1.把下一行指令的地址入棧
- 2.jmp跳轉
??臻g圖:
執(zhí)行call指令后,程序調到這里,不是sum函數(shù)的指令部分
在編譯階段,所有匯編指令代碼引用符號的地方全部不是合法的地址(因為我們當前文件可能引用外部的符號,而編譯階段是獨立編譯的,我們鏈接的時候才會進行符號解析、合并符號表等操作,之后再給符號分配內存地址),對于數(shù)據符號來說是零地址,對于函數(shù)符號來說是-4。那么當我們在鏈接階段符號解析完成以后,得到每一個符號的具體地址,給數(shù)據符號分配的地址是絕對地址,給函數(shù)符號分配的地址是與下一行指令地址的一個偏移量,這樣當程序需要跳轉到某個函數(shù)地址的時候,取出PC寄存器保存的地址與該偏移量相加就得到函數(shù)的入口地址。
計算相對偏移量后就進入了sum函數(shù),先執(zhí)行下面一段指令,才執(zhí)行我們寫的sum函數(shù)
每次執(zhí)行一個函數(shù)前要執(zhí)行三個操作:
- 把調用方的棧底地址入棧(push ebp),讓ebp指針指向當前函數(shù)的棧底(mov ebp,esp)
- 移動棧頂指針esp,給被調用函數(shù)開辟棧幀(sub esp 44h)
- 初始化新棧幀內存,把esp和ebp之間所有的棧內存全部初始化成0xCCCCCCCC(rep stos dword ptr [edi])無效值。
Linux為棧幀不分配初始值,windows會分配初始值,為0xCCCCCCCC(-858993460)
然后執(zhí)行sum函數(shù)的指令:
局部變量通過棧底指針ebp負向偏移訪問,形參通過ebp正向偏移訪問,eax為a+b的計算結果,將計算結果賦值給temp,return的時候將temp的值賦值給eax,給調用方返回
棧幀清退:
可以看到,開辟棧幀的時候我們對占內存進行了初始化,但是棧幀清退的時候僅僅就是修改了esp和ebp,沒有做其他任何操作,如果我們此時通過一些手段去訪問已被清退棧幀的內存,還是可以訪問到的,因為數(shù)據還存在
參數(shù)清除:
sum函數(shù)執(zhí)行完成后,從PC寄存器中取出地址繼續(xù)執(zhí)行,這里PC寄存器存放的是call指令的下一行地址,這條指令做的操作是回退形參變量占的內存,形參內存由調用方開辟和釋放。sum函數(shù)返回值由eax寄存器帶回來。
在被調用方執(zhí)行完成后,通過pop ebp就知道應該回到哪(恢復棧底),再通過ret指令就知道回到哪以后從哪一行指令開始運行(取出棧頂元素放到PC寄存器里面,而棧頂元素存的就是call的下一行地址)
sum函數(shù)棧底保存的是main的棧底地址,main函數(shù)棧底保存的是調用main的函數(shù)的棧底地址
二、C函數(shù)調用約定和返回值
函數(shù)調用約定和返回值
原文鏈接:https://blog.csdn.net/qq_41721746/article/details/124557537
相關推薦
- 2022-04-18 python中出現(xiàn)invalid?syntax報錯的幾種原因分析_python
- 2024-03-03 layuiAdmin側邊欄菜單刷新保持當前頁面
- 2022-06-14 GO語言協(xié)程創(chuàng)建使用并通過channel解決資源競爭_Golang
- 2022-03-26 Unity實現(xiàn)坦克模型_C#教程
- 2022-03-20 C++中vector容器的注意事項總結_C 語言
- 2022-07-03 關于python中range()的參數(shù)問題_python
- 2022-04-21 C語言中隨機數(shù)rand()函數(shù)詳解_C 語言
- 2022-11-09 React?錯誤邊界Error?Boundary使用示例解析_React
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據結構-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支