栈用来存放局部变量和形参以及状态指针,
可是很多时候变量是按程序逻辑动态创建的, 编译器不可能知道函数块需要多少局部变量啊?
比如这样一个函数:
void test()
{
if(someCondition) {double a = 123.0;} else {int b = 456;} // 栈的大小应按sizeof(a) 还是 sizeof(b)计算?
}
_test PROC ; Line 2 push ebp mov ebp, esp sub esp, 12 ; 0000000cH ................ ; Line 13 mov esp, ebp pop ebp ret 0 _test ENDP
编译器需要知道局部变量所需的总空间,即所有局部变量所需空间的和。楼主的例子里就是8+4=12
void test() { int a = 1; int b = 2; }
push ebp //*保存caller的frame基址, push操作会使栈顶寄存器ESP地址前移4Bytes mov ebp,esp //*原栈顶变为新frame的基址,因而栈是连续的 add esp,-0x08 //*栈顶前移8 Bytes, 预留变量a,b的空间,这证明frame的大小的确是编译时确定的. mov [ebp-0x04],0x00000001 //*局部变量a赋值1 mov [ebp-0x08],0x00000002 //*局部变量b赋值2 pop ecx //这个不明白了, ecx计数寄存器, 在这pop它干啥???? pop ecx // ????而且还pop两次????? pop ebp //*恢复caller的frame基址 ret //*返回
这里两次pop,就相当于add esp,8
编译器认为前者的速度和指令大小更划算
你观察一下,pop ecx 这条指令只需一个字节,而加减esp则需要三个字节
这种事情编译器常干
ecx在这里只是凑个位置,因为pop指令总需要个操作数
另外,#52楼的问题,其实ebp继续往前翻,不就可以“窥看”到返回地址/实参
#53楼,只需要把转换即可
int p;
_asm mov p,ebp
void* r=(void*)p;
函数调用的确是是个压栈的过程; 函数返回后,压栈的数据被弹出。
栈的大小,应该由编译器和os共同决定吧。
编译器中,有frame 和 record的概念。来控制函数调用和返回。以及发生异常时,栈的回退(unwinded)等。
函数调用中其相关信息和其局部变量的空间都在栈上分配,而且是编译器自动分配。
至于你说的是sizeof(a) 还是sizeof(b);个人觉得应该大于sizeof(a) + sizeof(b);
因为函数调用的过程,是一个上下文切换的过程,其间,首先要保存现场、然后将函数的返回地址及局部变量压栈、执行函数、返回、恢复现场。 如果写过汇编程序,这个就很容易理解了。
这些都是有编译器实现的,具体细节我们不必关心。
alloc 可以在栈上分配内存。你可以通过在函数内部定义一个足够大数组,来看看,你的编译器分配的stack size。
现代系统,都是一个线程一个栈,所以叫做线程栈,windows默认一个线程1MB的栈,每新开一个线程,就配套分配1MB的栈,当然也可以指定大小
注意区分两种存在方式:一个是在文件里,一个是在虚拟内存里
静态和全局变量保存在.exe/.dll文件的数据区,运行时则被加载到虚拟内存的某个区域,既不在堆,也不在栈
局部变量实质上是以代码的形式,即相关的push/pop指令,保存在.exe/.dll文件的代码区,运行时,代码被加载到虚拟内存的某个区。而执行代码里的push/pop时,局部变量被压入/弹出栈