[TOC]
# 棧的概念
在計算機中,棧可以理解為一個特殊的容器,用戶可以將數據依次放入棧中,然后再將數據按照相反的順序從棧中取出。也就是說,先放入的數據最后才能取出,而最后放入的數據必須先取出。這稱為先進后出(First In Last Out)原則。
放入數據常稱為入棧或壓棧(Push),取出數據常稱為出棧或彈出(Pop)。如下圖所示

可以發現,棧底始終不動,出棧入棧只是在移動棧頂,當棧中沒有數據時,棧頂和棧底重合。
從本質上來講,棧是一段連續的內存,需要同時記錄棧底和棧頂,才能對當前的棧進行定位。在現代計算機中,通常使用`ebp`寄存器指向棧底,而使用`esp`寄存器指向棧頂。隨著數據的進棧出棧,esp 的值會不斷變化,進棧時 esp 的值減小,出棧時 esp 的值增大。
> ebp 和 esp 都是CPU中的寄存器:ebp 是 Extend Base Pointer 的縮寫,通常用來指向棧底;esp 是 Extend Stack Pointer 的縮寫,通常用來指向棧頂。
如下圖所示是一個棧的實例:

# 棧的大小以及棧溢出
對每個程序來說,棧能使用的內存是有限的,一般是 1M~8M,這在編譯時就已經決定了,程序運行期間不能再改變。如果程序使用的棧內存超出最大值,就會發生棧溢出(Stack Overflow)錯誤。
> 一個程序可以包含多個線程,每個線程都有自己的棧,嚴格來說,棧的最大值是針對線程來說的,而不是針對程序。
棧內存的大小和編譯器有關,編譯器會為棧內存指定一個最大值,在 VC/VS 下,默認是 1M,在 C-Free 下,默認是 2M,在 Linux GCC 下,默認是 8M。
當然,我們也可以通過參數來修改棧內存的大小。以 VS2010 為例,在工程名處右擊,會彈出一個菜單,選擇“屬性”,會出現一個對話框,如下圖所示:

該圖中,我們將棧內存設置為 4M。提示:棧也經常被稱為堆棧,而堆依然稱為堆,所以堆棧這個概念并不包含堆,大家要注意區分
# 棧幀/活動記錄
當發生函數調用時,會將函數運行需要的信息全部壓入棧中,這常常被稱為棧幀(Stack Frame)或活動記錄(Activate Record)。活動記錄一般包括以下幾個方面的內容:
1. 函數的返回地址,也就是函數執行完成后從哪里開始繼續執行后面的代碼。例如:
~~~
int a, b, c;
func(1, 2);
c = a + b;
~~~
站在C語言的角度看,func() 函數執行完成后,會繼續執行`c=a+b;`語句,那么返回地址就是該語句在內存中的位置。
> 注意:C語言代碼最終會被編譯為機器指令,確切地說,返回地址應該是下一條指令的地址,這里之所以說是下一條C語言語句的地址,僅僅是為了更加直觀地說明問題。
2. 參數和局部變量。有些編譯器,或者編譯器在開啟優化選項的情況下,會通過寄存器來傳遞參數,而不是將參數壓入棧中,我們暫時不考慮這種情況。
3. 編譯器自動生成的臨時數據。例如,當函數返回值的長度較大(比如占用40個字節)時,會先將返回值壓入棧中,然后再交給函數調用者。
> 當返回值的長度較小(char、int、long 等)時,不會被壓入棧中,而是先將返回值放入寄存器,再傳遞給函數調用者。
4. 一些需要保存的寄存器,例如 ebp、ebx、esi、edi 等。之所以要保存寄存器的值,是為了在函數退出時能夠恢復到函數調用之前的場景,繼續執行上層函數。
下圖是一個函數調用的實例:

上圖是在Windows下使用VS2010 Debug模式編譯時一個函數所使用的棧內存,可以發現,理論上 ebp 寄存器應該指向棧底,但在實際應用中,它卻指向了old ebp。
> 在寄存器名字前面添加“old”,表示函數調用之前該寄存器的值。
當發生函數調用時:
* 實參、返回地址、ebp 寄存器首先入棧;
* 然后再分配一塊內存供局部變量、返回值等使用,這塊內存一般比較大,足以容納所有數據,并且會有冗余;
* 最后將其他寄存器的值壓入棧中。
需要注意的是,不同編譯器在不同編譯模式下所產生的函數棧并不完全相同,例如在VS2010下選擇Release模式,編譯器會進行大量優化,函數棧的樣貌蕩然無存,不具有教學意義,所以本教程以VS2010 Debug模式為例進行分析
# 關于數據的定位
由于 esp 的值會隨著數據的入棧而不斷變化,要想根據 esp 找到參數、局部變量等數據是比較困難的,所以在實現上是根據 ebp 來定位棧內數據的。ebp 的值是固定的,數據相對 ebp 的偏移也是固定的,ebp 的值加上偏移量就是數據的地址。
例如一個函數的定義如下:
~~~
void func(int a, int b){
float f = 28.5;
int n = 100;
//TODO:
}
~~~
調用形式為:
~~~
func(15, 92);
~~~
那么函數的活動記錄如下圖所示:

這里我們假設兩個局部變量挨著,并且第一個變量和 old ebp 也挨著(實際上它們之間有4個字節的空白),如此,第一個參數的地址是 ebp+12,第二個參數的地址是 ebp+8,第一個局部變量的地址是 ebp-4,第二個局部變量的地址是 ebp-8
- c語言
- 基礎知識
- 變量和常量
- 宏定義和預處理
- 隨機數
- register變量
- errno全局變量
- 靜態變量
- 類型
- 數組
- 類型轉換
- vs中c4996錯誤
- 數據類型和長度
- 二進制數,八進制數和十六進制數
- 位域
- typedef定義類型
- 函數和編譯
- 函數調用慣例
- 函數進棧和出棧
- 函數
- 編譯
- sizeof
- main函數接收參數
- 宏函數
- 目標文件和可執行文件有什么
- 強符號和弱符號
- 什么是鏈接
- 符號
- 強引用和弱引用
- 字符串處理函數
- sscanf
- 查找子字符串
- 字符串指針
- qt
- MFC
- 指針
- 簡介
- 指針詳解
- 案例
- 指針數組
- 偏移量
- 間接賦值
- 易錯點
- 二級指針
- 結構體指針
- 字節對齊
- 函數指針
- 指針例子
- main接收用戶輸入
- 內存布局
- 內存分區
- 空間開辟和釋放
- 堆空間操作字符串
- 內存處理函數
- 內存分頁
- 內存模型
- 棧
- 棧溢出攻擊
- 內存泄露
- 大小端存儲法
- 寄存器
- 結構體
- 共用體
- 枚舉
- 文件操作
- 文件到底是什么
- 文件打開和關閉
- 文件的順序讀寫
- 文件的隨機讀寫
- 文件復制
- FILE和緩沖區
- 文件大小
- 插入,刪除,更改文件內容
- typeid
- 內部鏈接和外部鏈接
- 動態庫
- 調試器
- 調試的概念
- vs調試
- 多文件編程
- extern關鍵字
- 頭文件規范
- 標準庫以及標準頭文件
- 頭文件只包含一次
- static
- 多線程
- 簡介
- 創建線程threads.h
- 創建線程pthread
- gdb
- 簡介
- mac使用gdb
- setjump和longjump
- 零拷貝
- gc
- 調試器原理
- c++
- c++簡介
- c++對c的擴展
- ::作用域運算符
- 名字控制
- cpp對c的增強
- const
- 變量定義數組
- 盡量以const替換#define
- 引用
- 內聯函數
- 函數默認參數
- 函數占位參數
- 函數重載
- extern "C"
- 類和對象
- 類封裝
- 構造和析構
- 深淺拷貝
- explicit關鍵字
- 動態對象創建
- 靜態成員
- 對象模型
- this
- 友元
- 單例
- 繼承
- 多態
- 運算符重載
- 賦值重載
- 指針運算符(*,->)重載
- 前置和后置++
- 左移<<運算符重載
- 函數調用符重載
- 總結
- bool重載
- 模板
- 簡介
- 普通函數和模板函數調用
- 模板的局限性
- 類模板
- 復數的模板類
- 類模板作為參數
- 類模板繼承
- 類模板類內和類外實現
- 類模板和友元函數
- 類模板實現數組
- 類型轉換
- 異常
- 異常基本語法
- 異常的接口聲明
- 異常的棧解旋
- 異常的多態
- 標準異常庫
- 自定義異常
- io
- 流的概念和類庫結構
- 標準io流
- 標準輸入流
- 標準輸出流
- 文件讀寫
- STL
- 簡介
- string容器
- vector容器
- deque容器
- stack容器
- queue容器
- list容器
- set/multiset容器
- map/multimap容器
- pair對組
- 深淺拷貝問題
- 使用時機
- 常用算法
- 函數對象
- 謂詞
- 內建函數對象
- 函數對象適配器
- 空間適配器
- 常用遍歷算法
- 查找算法
- 排序算法
- 拷貝和替換算法
- 算術生成算法
- 集合算法
- gcc
- GDB
- makefile
- visualstudio
- VisualAssistX
- 各種插件
- utf8編碼
- 制作安裝項目
- 編譯模式
- 內存對齊
- 快捷鍵
- 自動補全
- 查看c++類內存布局
- FFmpeg
- ffmpeg架構
- 命令的基本格式
- 分解與復用
- 處理原始數據
- 錄屏和音
- 濾鏡
- 水印
- 音視頻的拼接與裁剪
- 視頻圖片轉換
- 直播
- ffplay
- 常見問題
- 多媒體文件處理
- ffmpeg代碼結構
- 日志系統
- 處理流數據
- linux
- 系統調用
- 常用IO函數
- 文件操作函數
- 文件描述符復制
- 目錄相關操作
- 時間相關函數
- 進程
- valgrind
- 進程通信
- 信號
- 信號產生函數
- 信號集
- 信號捕捉
- SIGCHLD信號
- 不可重入函數和可重入函數
- 進程組
- 會話
- 守護進程
- 線程
- 線程屬性
- 互斥鎖
- 讀寫鎖
- 條件變量
- 信號量
- 網絡
- 分層模型
- 協議格式
- TCP協議
- socket
- socket概念
- 網絡字節序
- ip地址轉換函數
- sockaddr數據結構
- 網絡套接字函數
- socket模型創建流程圖
- socket函數
- bind函數
- listen函數
- accept函數
- connect函數
- C/S模型-TCP
- 出錯處理封裝函數
- 多進程并發服務器
- 多線程并發服務器
- 多路I/O復用服務器
- select
- poll
- epoll
- epoll事件
- epoll例子
- epoll反應堆思想
- udp
- socket IPC(本地套接字domain)
- 其他常用函數
- libevent
- libevent簡介