较旧的编译器通过将函数参数压入堆栈并在函数调用后从堆栈中弹出来为堆栈上的函数参数腾出空间;较新的编译器对此进行了优化。例如,当一个函数被执行时,堆栈改变如下:
start calling after before scanf after scanf
printf printf
+-----------+ +-----------+ +-----------+ +-----------+ +-----------+
|return addr| |return addr| |return addr| |return addr| |return addr|
+-----------+ +-----------+ +-----------+ +-----------+ +-----------+
| | | | | | | | | |
| local | | local | | local | | local | | local |
| variables | | variables | | variables | | variables | | variables |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
+-----------+ +-----------+ +-----------+ +-----------+ +-----------+
| "Enter.." | | password |
+-----------+ +-----------+
| "%s" |
+-----------+
您会看到sp(堆栈底部)如何随着每个函数调用而变化。较新的版本gcc改变了这一点;它们从一开始就在堆栈上留出足够的空间(用于局部变量和所有可能的函数参数),并将参数移动到相对于堆栈指针的地址:
start calling after before scanf after scanf
printf printf
+-----------+ +-----------+ +-----------+ +-----------+ +-----------+
|return addr| |return addr| |return addr| |return addr| |return addr|
+-----------+ +-----------+ +-----------+ +-----------+ +-----------+
| | | | | | | | | |
| local | | local | | local | | local | | local |
| variables | | variables | | variables | | variables | | variables |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
+-----------+ +-----------+ +-----------+ +-----------+ +-----------+
| | | | | | | password | | |
+-----------+ +-----------+ +-----------+ +-----------+ +-----------+
| | | "Enter.." | | | | "%s" | | |
+-----------+ +-----------+ +-----------+ +-----------+ +-----------+
请注意,从一开始,堆栈如何具有第 4 步(在 scanf 之前)所需的大小,以及“Enter..”字符串如何直接移动到堆栈指针所在的位置(因此它是堆栈上的第一个参数) ),而不是局部变量正下方的空格。
所以要从源代码计算堆栈大小,你需要知道
- 您的返回帧需要多少字节
- 为堆栈金丝雀保留多少字节(如果有)
- 局部变量需要多少字节
- 为函数参数保留多少字节;这可能包括对不遵循标准约定的结构或双打的特殊处理
- 从函数参数中减去多少字节,因为它们是在寄存器中传递的,这尤其适用于 64 位代码。
这也可能因编译器版本而异。您的编译器似乎为堆栈帧/金丝雀保留了 8 个字节;64 (0x40) 个字节用于密码数组,另外 16 个字节用于函数参数,其中 8 个就足够了(可能是出于对齐原因)?
我不会依赖任何公式来计算所需的字节数;相反,请检查我正在使用的特定编译器,并准备在使用不同编译器时更改此数字。