栈用来存放局部变量和形参以及状态指针,
可是很多时候变量是按程序逻辑动态创建的, 编译器不可能知道函数块需要多少局部变量啊?
比如这样一个函数:
void test()
{
if(someCondition) {double a = 123.0;} else {int b = 456;} // 栈的大小应按sizeof(a) 还是 sizeof(b)计算?
}
或者, 是我理解有误, 栈的大小可以伸缩?
66 个解决方案
#1
应该是默认分配给你一个大小的栈吧。 如你超过这个大小,就会运行报错.
#2
sizeof(a) + sizeof(b)
#3
编译器做的工作: 1. 词法提取和分析 2.语法分析 3 链接 4生成可执行程序代码
其中词法分析是将整个代码扫描一遍,提取变量……
堆栈的实际长度随着局部变量的增多而变长
可以确定的是, 编译器先提取变量, 后压入堆栈中。
置于填充堆栈内容,我觉得是 “生成可执行程序代码” 这一步要完成的事情。
其中词法分析是将整个代码扫描一遍,提取变量……
堆栈的实际长度随着局部变量的增多而变长
可以确定的是, 编译器先提取变量, 后压入堆栈中。
置于填充堆栈内容,我觉得是 “生成可执行程序代码” 这一步要完成的事情。
#4
栈的大小可以通过编译器选项指定。
在集成环境中,也可以可以做到,比如在VS2010中,可以:
(菜单)Project->工程名 Properties->Configuration Properties->Linker->System->***
也可以在代码中:
#pragma comment(linker, "/STACK:5000000 ")//用该语句设置/STACK:后面数字为容量
在集成环境中,也可以可以做到,比如在VS2010中,可以:
(菜单)Project->工程名 Properties->Configuration Properties->Linker->System->***
也可以在代码中:
#pragma comment(linker, "/STACK:5000000 ")//用该语句设置/STACK:后面数字为容量
#5
我不知道楼主所理解的“栈”是怎么个东西
反正我没听说过“确定栈的大小”这种行为
反正我没听说过“确定栈的大小”这种行为
#6
关于函数占用栈的空间大小,并没有一个硬性规定.编译器通常都是假设栈空间足够大.在你这个例子里,函数运行时使用的栈空间可以是sizeof(double)+sizeof(int),也可以是max( sizeof(double), sizeof(int) ).更可以根据运行的情况动态调整,即用的时候从栈中申请,用完就还回去.
函数中,需要放在栈中数据在代码敲定了以后,就是确定的了.当然你也可以手工从栈中分配内存,但这并不会影响函数中直接占用栈空间的对象数量.所以,编译器是明确知道一次函数调用需要多少栈空间的.
一个程序所能用的栈空间的大小也不是确定的.编译器一般允许用户设置栈的提交大小和保留大小.提交大小是指程序启动时,OS必须分配给程序的栈空间.而保留大小是表示保留一段连续的地址空间给栈,便于栈的生长.
而os在栈超出的时候还有可能动态增大栈的空间(甚至可以超出保留地址空间).但内存总有限制,栈溢出就会出错.这是也递归调用必须要限制深度的原因.
函数中,需要放在栈中数据在代码敲定了以后,就是确定的了.当然你也可以手工从栈中分配内存,但这并不会影响函数中直接占用栈空间的对象数量.所以,编译器是明确知道一次函数调用需要多少栈空间的.
一个程序所能用的栈空间的大小也不是确定的.编译器一般允许用户设置栈的提交大小和保留大小.提交大小是指程序启动时,OS必须分配给程序的栈空间.而保留大小是表示保留一段连续的地址空间给栈,便于栈的生长.
而os在栈超出的时候还有可能动态增大栈的空间(甚至可以超出保留地址空间).但内存总有限制,栈溢出就会出错.这是也递归调用必须要限制深度的原因.
#7
堆栈的容量一般由编译器指定,很多编译器也留了选项供程序员设定。但编译器出于性能考虑一般不检测栈边界,那么当栈溢出的时候会发生什么呢?不同的执行环境、不同的栈实现会有所不同,可能啥事也不会发生,可能会覆盖了其它有效数据,也可能产生CPU异常等等。
#8
6楼说得很明白了!
#9
我的意思是, 每次调用函数时压栈, 被压入的那个元素的大小. 而不是程序总的栈空间.
不可能每个栈元素的大小是一样的吧?
#10
看错了。。。
栈的大小一般可以通过编译器开关/STACK:设置,不然默认的是 1 MB
==============================
如果你想说的是,为:
void test()
{
if(someCondition) {double a = 123.0;} else {int b = 456;} // 栈的大小应按sizeof(a) 还是 sizeof(b)计算?
}
这样一段程序,分配了多少空间,
那就是sizeof(a) + sizeof(b)
#11
push进入的当然不是“总的栈空间”,完全不是同一个概念。而push的也不一定是对象的大小,就是说,不一定是sizeof(a)+sizeof(b),因为出于性能考虑,压入栈的元素通常都会进行内存对齐,压入栈的东西可能会包含内存对齐产生的空隙,空隙的大小要看对齐系数。
#12
栈的内容在不断的变化,只要留出足够的空间即可。
#13
void test()
{
if(someCondition) {double a = 123.0;} else {int b = 456;} // 栈的大小应按sizeof(a) 还是 sizeof(b)计算?
}
----------------------
你要明确, 函数的所有局部变量都是进入函数的时候分配的. 并不是你定义的时候才分配!
所以, 无论 someCondition 是否成立, double a 和 int b 变量都会被分配. 而不是根据这个条件, 满足的时候分配 a, 不满足的时候分配 b.
{
if(someCondition) {double a = 123.0;} else {int b = 456;} // 栈的大小应按sizeof(a) 还是 sizeof(b)计算?
}
----------------------
你要明确, 函数的所有局部变量都是进入函数的时候分配的. 并不是你定义的时候才分配!
所以, 无论 someCondition 是否成立, double a 和 int b 变量都会被分配. 而不是根据这个条件, 满足的时候分配 a, 不满足的时候分配 b.
#14
看汇编 12 + 4
_test PROC
; Line 2
push ebp
mov ebp, esp
sub esp, 12 ; 0000000cH
; Line 3
cmp DWORD PTR _b$[ebp], 0
je SHORT $LN2@test
; Line 5
fld QWORD PTR __real@405ec00000000000
fstp QWORD PTR _a$907[ebp]
; Line 6
lea eax, DWORD PTR _a$907[ebp]
push eax
push OFFSET $SG909
call _printf
add esp, 8
; Line 8
jmp SHORT $LN1@test
$LN2@test:
; Line 10
mov DWORD PTR _b$911[ebp], 456 ; 000001c8H
; Line 11
lea ecx, DWORD PTR _b$911[ebp]
push ecx
push OFFSET $SG912
call _printf
add esp, 8
$LN1@test:
; Line 13
mov esp, ebp
pop ebp
ret 0
_test ENDP
#15
栈的大小肯定不是估算出来的,而且所谓足够的,我很怀疑。
WCHAR szFileName[MAX_PATH] = {}
我经常这么用,这个栈就大了,栈的大小肯定是编译器通过函数的参数,局部变量的和来处理的。
只可能比这个和大,因为调用一个函数的时候,有些寄存器的值也要PUSH的。
---------------------------
C++和C判断栈需要多大的空间也是相同的逻辑,只不过C++可以不在函数的开始就声明,但是不影响计算栈的空间。
---------------------------
而且汇编指令ret N,也说明这个空间的大小是编译的时候就知道的。
WCHAR szFileName[MAX_PATH] = {}
我经常这么用,这个栈就大了,栈的大小肯定是编译器通过函数的参数,局部变量的和来处理的。
只可能比这个和大,因为调用一个函数的时候,有些寄存器的值也要PUSH的。
---------------------------
C++和C判断栈需要多大的空间也是相同的逻辑,只不过C++可以不在函数的开始就声明,但是不影响计算栈的空间。
---------------------------
而且汇编指令ret N,也说明这个空间的大小是编译的时候就知道的。
#16
google stack, frame,
or
找本 深入理解计算机系统(2004)扫描版 3.7.1 Stack Frame Structure
or
找本 深入理解计算机系统(2004)扫描版 3.7.1 Stack Frame Structure
#17
操作栈的都属于处理器底层命令了,我觉得应该会通过一些列命令进行动态分配,栈本身有一个容量,应该是1MB,在这个容量内会不断动态分配,多了就加一个空间在放。 但是要是超了就会崩溃。
此情形参照VECTOR<>
此情形参照VECTOR<>
#18
偶尔会遇到栈不够用的情况,只需要更改编译器的配置即可。
#19
无汇编,无真相。
《深度探索C++对象模型》
《深度探索C++对象模型》
#20
当someCondition为真,a入栈,否则b入栈。出作用域时,再分别出栈即可
完全无需知道需要多少栈空间
因为寄存器ebp记录着当下函数的栈空间的起始值,而此起始处里就记录着上次ebp的值和当下函数返回后应该回去的地址
本次函数结束时,依次出栈,或者根据ebp定位一次性跳到所需的位置即可
总之,入多少,出多少,即可
#21
good point, 在链接时确定。
#22
good point, 在链接时指定(如果未指定,就用默认值,默认值大小跟编译器厂商有关)。
#23
有意思的观点.
那么, static局部变量呢, 它们被存放在全局数据区.
是编译时在全局数据区预留了空间, 还是全局数据区运行时可伸缩?
#24
注意区分两种存在方式:一个是在文件里,一个是在虚拟内存里
静态和全局变量保存在.exe/.dll文件的数据区,运行时则被加载到虚拟内存的某个区域,既不在堆,也不在栈
局部变量实质上是以代码的形式,即相关的push/pop指令,保存在.exe/.dll文件的代码区,运行时,代码被加载到虚拟内存的某个区。而执行代码里的push/pop时,局部变量被压入/弹出栈
编译时,.exe/.dll文件里保存了相关的数据,以告诉操作系统加载它的时候怎样分配栈和堆,默认都是1MB。于是,系统加载该文件时,在虚拟内存里分别预留1MB给栈和堆。运行时,这1MB栈用完了就会溢出。堆则不然,用完了预留的,则会自动再向系统申请,直到申请不到
#25
更正#20楼
编译器需要知道局部变量所需的总空间,即所有局部变量所需空间的和。楼主的例子里就是8+4=12
#26
更正#24楼
注意区分两种存在方式:一种是在文件里,一种是在虚拟内存里
静态和全局变量保存在.exe/.dll文件的数据区,运行时则被加载到虚拟内存的某个区域,既不在堆,也不在栈
文件里不存在局部变量区
局部变量实质上是以代码的形式,即相关的等价于push/pop的指令,保存在.exe/.dll文件的代码区,运行时,代码被加载到虚拟内存的某个区。而执行代码里的等价于push/pop的指令,局部变量被压入/弹出栈
所谓等价是指,比如楼主的例子里,编译器直接用sub esp,12指令,这相当于一次性push了12字节
编译时,.exe/.dll文件里保存了相关的数据,以告诉操作系统加载它的时候怎样分配栈和堆,默认都是1MB。于是,系统加载该文件时,在虚拟内存里分别预留1MB给栈和堆。运行时,这1MB栈用完了就会溢出。堆则不然,用完了预留的,则会自动再向系统申请,直到申请不到
注意区分两种存在方式:一种是在文件里,一种是在虚拟内存里
静态和全局变量保存在.exe/.dll文件的数据区,运行时则被加载到虚拟内存的某个区域,既不在堆,也不在栈
文件里不存在局部变量区
局部变量实质上是以代码的形式,即相关的等价于push/pop的指令,保存在.exe/.dll文件的代码区,运行时,代码被加载到虚拟内存的某个区。而执行代码里的等价于push/pop的指令,局部变量被压入/弹出栈
所谓等价是指,比如楼主的例子里,编译器直接用sub esp,12指令,这相当于一次性push了12字节
编译时,.exe/.dll文件里保存了相关的数据,以告诉操作系统加载它的时候怎样分配栈和堆,默认都是1MB。于是,系统加载该文件时,在虚拟内存里分别预留1MB给栈和堆。运行时,这1MB栈用完了就会溢出。堆则不然,用完了预留的,则会自动再向系统申请,直到申请不到
#27
也就是说, 还是要为所有变量预留空间,不管它会不会在运行时被创建? 怎样用代码去窥探/证实这一点呢?
另外, 局部变量应该没有"退栈"一说吧, 函数退栈是不会做任何清理的.
void A(){int a = 123;}
void B(){int b; cout<<b<<endl;}
int main(int argc, char** argv)
{
A();
B();
return 0;
}
输出为 123;
#28
编译器可以控制栈大小,一些用默认值。所以写递归函数之类的东西得注意点。
#29
如果不优化的话,一般是这样
#14楼给出了你的例子的汇编
下面给个vs2008例子,release下编译 /Od 即禁用优化
int u=0,v=0;
int main()
{
_asm
{
mov u,esp;
mov v,ebp;
}
cout <<v-u <<endl; // 预留了24字节,即6个int
if(true)
{
int x=5,y;
int z=x;
}
else
{
int a=2,b;
int c=a-a;
}
return 0;
}
#30
退栈只是增加esp,如果需要的话,把当下esp处的值弹到需要的地方,但并不清理栈
#31
#32
函数的返回值地址 所占用的空间 都没有加吗?
怎么可能是简答的 sizeof (a +b) 呢?
someCondition 如果是局部变量的话, 也得加上它的值,
如果是函数的话, 加上 跳转地址吧?
怎么可能是简答的 sizeof (a +b) 呢?
someCondition 如果是局部变量的话, 也得加上它的值,
如果是函数的话, 加上 跳转地址吧?
#33
阁下的观点不敢苟同,对于cpu来说根本没有什么所谓的代码形式,全是二进制数据而已。
而且阁下存在方式的区分也并不正确,无论局部变量或者全局变量亦或者静态变量,只要初始化的都在.data段,或者.rdata段,未初始化的全局变量在.bss段。
其他部分不做评价,请google变量及函数的重定位。
#34
不论是什么对象,总有作用域范围,在C++里就是一对大括号(全局、静态除外)里,在声明变量时push,在出作用域时pop,这种处理对于编译器应该是小菜一碟。
不知道我理解对不对
不知道我理解对不对
#35
你的意思是下例的{1,2,3}保存在.data段?
int func()
{
int temp[]={1,2,3}; // {1,2,3}保存在.data段?
return temp[2];
}
int main()
{
int x=func();
return 0;
}
#36
似乎是进入函数时一次性push全部局部变量所需的空间,而不是用一个push一个
#37
下例的字符串just a teststring保存在.rdata区,这是因为编译器将字符串做为全局只读数据保存,然后在运行时再把此数据从.rdata区读入局部变量
倘若写成char c[]={'j','u','s','t',…… '\0'};的形式,也就不存在了。正如#35楼的例子
但是无论哪种形式,绝不会有一个局部变量名c和x保存在.exe/.dll文件里
int func()
{
char c[]="just a teststring";
return strlen(c);
}
int main()
{
int x=func();
return 0;
}
#38
链接视图和装载视图不是一个概念,编译完了之后已经在.rdata了,链接视图的段和装载视图的段是有区别的
#39
楼上认为非静态局部变量是保存在文件里的?
就举最简单的例子吧
这里的 7 保存在文件的.rdata段,temp 名称保存在符号表里?然后文件里是不是还有一个4字节的空间留给它?
呵呵
否则的话,实在不知道和我说的有什么冲突
就举最简单的例子吧
int func()
{
int temp=7;
temp+=temp;
return temp;
}
这里的 7 保存在文件的.rdata段,temp 名称保存在符号表里?然后文件里是不是还有一个4字节的空间留给它?
呵呵
否则的话,实在不知道和我说的有什么冲突
#40
int func()
{
int temp=7;
temp+=temp;
return temp;
}
用OD查看反汇编
Address Hex dump Command
00401000 /$ 55 PUSH EBP
00401001 |. 8BEC MOV EBP,ESP
00401003 |. 51 PUSH ECX
00401004 |? C745 FC 07000000 MOV DWORD PTR SS:[EBP-4],7
0040100B |? 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]
0040100E |? 0345 FC ADD EAX,DWORD PTR SS:[EBP-4]
00401011 |? 8945 FC MOV DWORD PTR SS:[EBP-4],EAX
00401014 |. 8B45 FC MOV EAX,DWORD PTR SS:[LOCAL.1]
00401017 |? 8BE5 MOV ESP,EBP
00401019 |? 5D POP EBP
0040101A |? C3 RETN
显然7在文件上的存在形式就是代码区的00401004处这一行指令,即机器语言的 C745 FC 07000 里的 07000000
而temp符号根本不存在,因为一个非静态的局部变量需要符号做什么呢!?
运行时,内存里用来存放7的栈位置就是ebp-4
对temp和7来说,这应该就是全部了,哪里有什么.data,.rdata,.bss ?!
我前面说,局部变量是以代码的方式保存在文件的代码区的,虽然说法很粗糙,但意思没什么错吧?
#41
SS:[EBP-4] 这个是temp的地址,7只是个立即数。
变成汇编之后本来就不存在.rdata或者其他什么,你看不到而已
你可以使用工具查看你的可执行文件,windows下面我不太清楚是什么,pedump应该可以
linux下
objdump
readelf
都可以查看
对这个问题只是个探讨而已,有的我也不是很熟,书刚看到这里,还没完全了然于胸,共同学习。
#42
windows下dumpbin即可
编译/汇编之后,文件里当然存在.rdata(如果有只读数据的话)。加载到内存里也有对应的一个区
只要有初始化的全局/静态数据,编译/汇编后就存放在文件里的.data区。加载到内存里也是对应的一个区
但是,文件里没有局部变量区
不是看不到,是根本没有
另外,ss,ebp,是运行时用于定位局部变量在栈的位置,而不是局部变量在磁盘文件里的位置
#43
有线程栈,我自己理解的“函数栈”是线程栈的子站
线程栈是编译阶段定死的,windows默认貌似4MB,这个在编译选项中可调的
“函数栈”又分两个部分,第一是参数,第二是函数的局部变量。
参数是需要随着函数调用入栈出栈的,一个函数的局部变量也可以看成是一个元素,函数调用的时候入栈
在一个内部来说,每个句柄变量也是在栈上,位置在编译阶段就定好了
线程栈是编译阶段定死的,windows默认貌似4MB,这个在编译选项中可调的
“函数栈”又分两个部分,第一是参数,第二是函数的局部变量。
参数是需要随着函数调用入栈出栈的,一个函数的局部变量也可以看成是一个元素,函数调用的时候入栈
在一个内部来说,每个句柄变量也是在栈上,位置在编译阶段就定好了
#44
#45
没错函数调用的确是是个压栈的过程; 函数返回后,压栈的数据被弹出。
栈的大小,应该由编译器和os共同决定吧。
编译器中,有frame 和 record的概念。来控制函数调用和返回。以及发生异常时,栈的回退(unwinded)等。
函数调用中其相关信息和其局部变量的空间都在栈上分配,而且是编译器自动分配。
至于你说的是sizeof(a) 还是sizeof(b);个人觉得应该大于sizeof(a) + sizeof(b);
因为函数调用的过程,是一个上下文切换的过程,其间,首先要保存现场、然后将函数的返回地址及局部变量压栈、执行函数、返回、恢复现场。 如果写过汇编程序,这个就很容易理解了。
这些都是有编译器实现的,具体细节我们不必关心。
alloc 可以在栈上分配内存。你可以通过在函数内部定义一个足够大数组,来看看,你的编译器分配的stack size。
栈的大小,应该由编译器和os共同决定吧。
编译器中,有frame 和 record的概念。来控制函数调用和返回。以及发生异常时,栈的回退(unwinded)等。
函数调用中其相关信息和其局部变量的空间都在栈上分配,而且是编译器自动分配。
至于你说的是sizeof(a) 还是sizeof(b);个人觉得应该大于sizeof(a) + sizeof(b);
因为函数调用的过程,是一个上下文切换的过程,其间,首先要保存现场、然后将函数的返回地址及局部变量压栈、执行函数、返回、恢复现场。 如果写过汇编程序,这个就很容易理解了。
这些都是有编译器实现的,具体细节我们不必关心。
alloc 可以在栈上分配内存。你可以通过在函数内部定义一个足够大数组,来看看,你的编译器分配的stack size。
#46
哥决定先去学汇编.
#47
栈的大小也可以动态设置吧。CreateThread(...)就可以设置栈的大小
#48
...
简单点 花点时间了解下ebp,esp寄存器的用途,用Ollydbg随便打开一个程序,单步跟踪下函数调用的过程,观察栈的变化 就明白了
简单点 花点时间了解下ebp,esp寄存器的用途,用Ollydbg随便打开一个程序,单步跟踪下函数调用的过程,观察栈的变化 就明白了
#49
"或者, 是我理解有误, 栈的大小可以伸缩?"
这里边还有个栈大小,和实际用了的栈大小。
正是应为编译器不知道程序运行的时候栈有多大,才会有栈溢出这一说,就是实际用的栈空间超过了编译器分配的栈空间,程序就崩溃了。
这里边还有个栈大小,和实际用了的栈大小。
正是应为编译器不知道程序运行的时候栈有多大,才会有栈溢出这一说,就是实际用的栈空间超过了编译器分配的栈空间,程序就崩溃了。
#50
没有所谓的函数栈
线程栈就是栈,只不过是不同的上下文中的术语而已
栈,针对编译原理
线程栈,针对操作系统
现代系统,都是一个线程一个栈,所以叫做线程栈,windows默认一个线程1MB的栈,每新开一个线程,就配套分配1MB的栈,当然也可以指定大小
#1
应该是默认分配给你一个大小的栈吧。 如你超过这个大小,就会运行报错.
#2
sizeof(a) + sizeof(b)
#3
编译器做的工作: 1. 词法提取和分析 2.语法分析 3 链接 4生成可执行程序代码
其中词法分析是将整个代码扫描一遍,提取变量……
堆栈的实际长度随着局部变量的增多而变长
可以确定的是, 编译器先提取变量, 后压入堆栈中。
置于填充堆栈内容,我觉得是 “生成可执行程序代码” 这一步要完成的事情。
其中词法分析是将整个代码扫描一遍,提取变量……
堆栈的实际长度随着局部变量的增多而变长
可以确定的是, 编译器先提取变量, 后压入堆栈中。
置于填充堆栈内容,我觉得是 “生成可执行程序代码” 这一步要完成的事情。
#4
栈的大小可以通过编译器选项指定。
在集成环境中,也可以可以做到,比如在VS2010中,可以:
(菜单)Project->工程名 Properties->Configuration Properties->Linker->System->***
也可以在代码中:
#pragma comment(linker, "/STACK:5000000 ")//用该语句设置/STACK:后面数字为容量
在集成环境中,也可以可以做到,比如在VS2010中,可以:
(菜单)Project->工程名 Properties->Configuration Properties->Linker->System->***
也可以在代码中:
#pragma comment(linker, "/STACK:5000000 ")//用该语句设置/STACK:后面数字为容量
#5
我不知道楼主所理解的“栈”是怎么个东西
反正我没听说过“确定栈的大小”这种行为
反正我没听说过“确定栈的大小”这种行为
#6
关于函数占用栈的空间大小,并没有一个硬性规定.编译器通常都是假设栈空间足够大.在你这个例子里,函数运行时使用的栈空间可以是sizeof(double)+sizeof(int),也可以是max( sizeof(double), sizeof(int) ).更可以根据运行的情况动态调整,即用的时候从栈中申请,用完就还回去.
函数中,需要放在栈中数据在代码敲定了以后,就是确定的了.当然你也可以手工从栈中分配内存,但这并不会影响函数中直接占用栈空间的对象数量.所以,编译器是明确知道一次函数调用需要多少栈空间的.
一个程序所能用的栈空间的大小也不是确定的.编译器一般允许用户设置栈的提交大小和保留大小.提交大小是指程序启动时,OS必须分配给程序的栈空间.而保留大小是表示保留一段连续的地址空间给栈,便于栈的生长.
而os在栈超出的时候还有可能动态增大栈的空间(甚至可以超出保留地址空间).但内存总有限制,栈溢出就会出错.这是也递归调用必须要限制深度的原因.
函数中,需要放在栈中数据在代码敲定了以后,就是确定的了.当然你也可以手工从栈中分配内存,但这并不会影响函数中直接占用栈空间的对象数量.所以,编译器是明确知道一次函数调用需要多少栈空间的.
一个程序所能用的栈空间的大小也不是确定的.编译器一般允许用户设置栈的提交大小和保留大小.提交大小是指程序启动时,OS必须分配给程序的栈空间.而保留大小是表示保留一段连续的地址空间给栈,便于栈的生长.
而os在栈超出的时候还有可能动态增大栈的空间(甚至可以超出保留地址空间).但内存总有限制,栈溢出就会出错.这是也递归调用必须要限制深度的原因.
#7
堆栈的容量一般由编译器指定,很多编译器也留了选项供程序员设定。但编译器出于性能考虑一般不检测栈边界,那么当栈溢出的时候会发生什么呢?不同的执行环境、不同的栈实现会有所不同,可能啥事也不会发生,可能会覆盖了其它有效数据,也可能产生CPU异常等等。
#8
6楼说得很明白了!
#9
我的意思是, 每次调用函数时压栈, 被压入的那个元素的大小. 而不是程序总的栈空间.
不可能每个栈元素的大小是一样的吧?
#10
看错了。。。
栈的大小一般可以通过编译器开关/STACK:设置,不然默认的是 1 MB
==============================
如果你想说的是,为:
void test()
{
if(someCondition) {double a = 123.0;} else {int b = 456;} // 栈的大小应按sizeof(a) 还是 sizeof(b)计算?
}
这样一段程序,分配了多少空间,
那就是sizeof(a) + sizeof(b)
#11
push进入的当然不是“总的栈空间”,完全不是同一个概念。而push的也不一定是对象的大小,就是说,不一定是sizeof(a)+sizeof(b),因为出于性能考虑,压入栈的元素通常都会进行内存对齐,压入栈的东西可能会包含内存对齐产生的空隙,空隙的大小要看对齐系数。
#12
栈的内容在不断的变化,只要留出足够的空间即可。
#13
void test()
{
if(someCondition) {double a = 123.0;} else {int b = 456;} // 栈的大小应按sizeof(a) 还是 sizeof(b)计算?
}
----------------------
你要明确, 函数的所有局部变量都是进入函数的时候分配的. 并不是你定义的时候才分配!
所以, 无论 someCondition 是否成立, double a 和 int b 变量都会被分配. 而不是根据这个条件, 满足的时候分配 a, 不满足的时候分配 b.
{
if(someCondition) {double a = 123.0;} else {int b = 456;} // 栈的大小应按sizeof(a) 还是 sizeof(b)计算?
}
----------------------
你要明确, 函数的所有局部变量都是进入函数的时候分配的. 并不是你定义的时候才分配!
所以, 无论 someCondition 是否成立, double a 和 int b 变量都会被分配. 而不是根据这个条件, 满足的时候分配 a, 不满足的时候分配 b.
#14
看汇编 12 + 4
_test PROC
; Line 2
push ebp
mov ebp, esp
sub esp, 12 ; 0000000cH
; Line 3
cmp DWORD PTR _b$[ebp], 0
je SHORT $LN2@test
; Line 5
fld QWORD PTR __real@405ec00000000000
fstp QWORD PTR _a$907[ebp]
; Line 6
lea eax, DWORD PTR _a$907[ebp]
push eax
push OFFSET $SG909
call _printf
add esp, 8
; Line 8
jmp SHORT $LN1@test
$LN2@test:
; Line 10
mov DWORD PTR _b$911[ebp], 456 ; 000001c8H
; Line 11
lea ecx, DWORD PTR _b$911[ebp]
push ecx
push OFFSET $SG912
call _printf
add esp, 8
$LN1@test:
; Line 13
mov esp, ebp
pop ebp
ret 0
_test ENDP
#15
栈的大小肯定不是估算出来的,而且所谓足够的,我很怀疑。
WCHAR szFileName[MAX_PATH] = {}
我经常这么用,这个栈就大了,栈的大小肯定是编译器通过函数的参数,局部变量的和来处理的。
只可能比这个和大,因为调用一个函数的时候,有些寄存器的值也要PUSH的。
---------------------------
C++和C判断栈需要多大的空间也是相同的逻辑,只不过C++可以不在函数的开始就声明,但是不影响计算栈的空间。
---------------------------
而且汇编指令ret N,也说明这个空间的大小是编译的时候就知道的。
WCHAR szFileName[MAX_PATH] = {}
我经常这么用,这个栈就大了,栈的大小肯定是编译器通过函数的参数,局部变量的和来处理的。
只可能比这个和大,因为调用一个函数的时候,有些寄存器的值也要PUSH的。
---------------------------
C++和C判断栈需要多大的空间也是相同的逻辑,只不过C++可以不在函数的开始就声明,但是不影响计算栈的空间。
---------------------------
而且汇编指令ret N,也说明这个空间的大小是编译的时候就知道的。
#16
google stack, frame,
or
找本 深入理解计算机系统(2004)扫描版 3.7.1 Stack Frame Structure
or
找本 深入理解计算机系统(2004)扫描版 3.7.1 Stack Frame Structure
#17
操作栈的都属于处理器底层命令了,我觉得应该会通过一些列命令进行动态分配,栈本身有一个容量,应该是1MB,在这个容量内会不断动态分配,多了就加一个空间在放。 但是要是超了就会崩溃。
此情形参照VECTOR<>
此情形参照VECTOR<>
#18
偶尔会遇到栈不够用的情况,只需要更改编译器的配置即可。
#19
无汇编,无真相。
《深度探索C++对象模型》
《深度探索C++对象模型》
#20
当someCondition为真,a入栈,否则b入栈。出作用域时,再分别出栈即可
完全无需知道需要多少栈空间
因为寄存器ebp记录着当下函数的栈空间的起始值,而此起始处里就记录着上次ebp的值和当下函数返回后应该回去的地址
本次函数结束时,依次出栈,或者根据ebp定位一次性跳到所需的位置即可
总之,入多少,出多少,即可
#21
good point, 在链接时确定。
#22
good point, 在链接时指定(如果未指定,就用默认值,默认值大小跟编译器厂商有关)。
#23
有意思的观点.
那么, static局部变量呢, 它们被存放在全局数据区.
是编译时在全局数据区预留了空间, 还是全局数据区运行时可伸缩?
#24
注意区分两种存在方式:一个是在文件里,一个是在虚拟内存里
静态和全局变量保存在.exe/.dll文件的数据区,运行时则被加载到虚拟内存的某个区域,既不在堆,也不在栈
局部变量实质上是以代码的形式,即相关的push/pop指令,保存在.exe/.dll文件的代码区,运行时,代码被加载到虚拟内存的某个区。而执行代码里的push/pop时,局部变量被压入/弹出栈
编译时,.exe/.dll文件里保存了相关的数据,以告诉操作系统加载它的时候怎样分配栈和堆,默认都是1MB。于是,系统加载该文件时,在虚拟内存里分别预留1MB给栈和堆。运行时,这1MB栈用完了就会溢出。堆则不然,用完了预留的,则会自动再向系统申请,直到申请不到
#25
更正#20楼
编译器需要知道局部变量所需的总空间,即所有局部变量所需空间的和。楼主的例子里就是8+4=12
#26
更正#24楼
注意区分两种存在方式:一种是在文件里,一种是在虚拟内存里
静态和全局变量保存在.exe/.dll文件的数据区,运行时则被加载到虚拟内存的某个区域,既不在堆,也不在栈
文件里不存在局部变量区
局部变量实质上是以代码的形式,即相关的等价于push/pop的指令,保存在.exe/.dll文件的代码区,运行时,代码被加载到虚拟内存的某个区。而执行代码里的等价于push/pop的指令,局部变量被压入/弹出栈
所谓等价是指,比如楼主的例子里,编译器直接用sub esp,12指令,这相当于一次性push了12字节
编译时,.exe/.dll文件里保存了相关的数据,以告诉操作系统加载它的时候怎样分配栈和堆,默认都是1MB。于是,系统加载该文件时,在虚拟内存里分别预留1MB给栈和堆。运行时,这1MB栈用完了就会溢出。堆则不然,用完了预留的,则会自动再向系统申请,直到申请不到
注意区分两种存在方式:一种是在文件里,一种是在虚拟内存里
静态和全局变量保存在.exe/.dll文件的数据区,运行时则被加载到虚拟内存的某个区域,既不在堆,也不在栈
文件里不存在局部变量区
局部变量实质上是以代码的形式,即相关的等价于push/pop的指令,保存在.exe/.dll文件的代码区,运行时,代码被加载到虚拟内存的某个区。而执行代码里的等价于push/pop的指令,局部变量被压入/弹出栈
所谓等价是指,比如楼主的例子里,编译器直接用sub esp,12指令,这相当于一次性push了12字节
编译时,.exe/.dll文件里保存了相关的数据,以告诉操作系统加载它的时候怎样分配栈和堆,默认都是1MB。于是,系统加载该文件时,在虚拟内存里分别预留1MB给栈和堆。运行时,这1MB栈用完了就会溢出。堆则不然,用完了预留的,则会自动再向系统申请,直到申请不到
#27
也就是说, 还是要为所有变量预留空间,不管它会不会在运行时被创建? 怎样用代码去窥探/证实这一点呢?
另外, 局部变量应该没有"退栈"一说吧, 函数退栈是不会做任何清理的.
void A(){int a = 123;}
void B(){int b; cout<<b<<endl;}
int main(int argc, char** argv)
{
A();
B();
return 0;
}
输出为 123;
#28
编译器可以控制栈大小,一些用默认值。所以写递归函数之类的东西得注意点。
#29
如果不优化的话,一般是这样
#14楼给出了你的例子的汇编
下面给个vs2008例子,release下编译 /Od 即禁用优化
int u=0,v=0;
int main()
{
_asm
{
mov u,esp;
mov v,ebp;
}
cout <<v-u <<endl; // 预留了24字节,即6个int
if(true)
{
int x=5,y;
int z=x;
}
else
{
int a=2,b;
int c=a-a;
}
return 0;
}
#30
退栈只是增加esp,如果需要的话,把当下esp处的值弹到需要的地方,但并不清理栈
#31
#32
函数的返回值地址 所占用的空间 都没有加吗?
怎么可能是简答的 sizeof (a +b) 呢?
someCondition 如果是局部变量的话, 也得加上它的值,
如果是函数的话, 加上 跳转地址吧?
怎么可能是简答的 sizeof (a +b) 呢?
someCondition 如果是局部变量的话, 也得加上它的值,
如果是函数的话, 加上 跳转地址吧?
#33
阁下的观点不敢苟同,对于cpu来说根本没有什么所谓的代码形式,全是二进制数据而已。
而且阁下存在方式的区分也并不正确,无论局部变量或者全局变量亦或者静态变量,只要初始化的都在.data段,或者.rdata段,未初始化的全局变量在.bss段。
其他部分不做评价,请google变量及函数的重定位。
#34
不论是什么对象,总有作用域范围,在C++里就是一对大括号(全局、静态除外)里,在声明变量时push,在出作用域时pop,这种处理对于编译器应该是小菜一碟。
不知道我理解对不对
不知道我理解对不对
#35
你的意思是下例的{1,2,3}保存在.data段?
int func()
{
int temp[]={1,2,3}; // {1,2,3}保存在.data段?
return temp[2];
}
int main()
{
int x=func();
return 0;
}
#36
似乎是进入函数时一次性push全部局部变量所需的空间,而不是用一个push一个
#37
下例的字符串just a teststring保存在.rdata区,这是因为编译器将字符串做为全局只读数据保存,然后在运行时再把此数据从.rdata区读入局部变量
倘若写成char c[]={'j','u','s','t',…… '\0'};的形式,也就不存在了。正如#35楼的例子
但是无论哪种形式,绝不会有一个局部变量名c和x保存在.exe/.dll文件里
int func()
{
char c[]="just a teststring";
return strlen(c);
}
int main()
{
int x=func();
return 0;
}
#38
链接视图和装载视图不是一个概念,编译完了之后已经在.rdata了,链接视图的段和装载视图的段是有区别的
#39
楼上认为非静态局部变量是保存在文件里的?
就举最简单的例子吧
这里的 7 保存在文件的.rdata段,temp 名称保存在符号表里?然后文件里是不是还有一个4字节的空间留给它?
呵呵
否则的话,实在不知道和我说的有什么冲突
就举最简单的例子吧
int func()
{
int temp=7;
temp+=temp;
return temp;
}
这里的 7 保存在文件的.rdata段,temp 名称保存在符号表里?然后文件里是不是还有一个4字节的空间留给它?
呵呵
否则的话,实在不知道和我说的有什么冲突
#40
int func()
{
int temp=7;
temp+=temp;
return temp;
}
用OD查看反汇编
Address Hex dump Command
00401000 /$ 55 PUSH EBP
00401001 |. 8BEC MOV EBP,ESP
00401003 |. 51 PUSH ECX
00401004 |? C745 FC 07000000 MOV DWORD PTR SS:[EBP-4],7
0040100B |? 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]
0040100E |? 0345 FC ADD EAX,DWORD PTR SS:[EBP-4]
00401011 |? 8945 FC MOV DWORD PTR SS:[EBP-4],EAX
00401014 |. 8B45 FC MOV EAX,DWORD PTR SS:[LOCAL.1]
00401017 |? 8BE5 MOV ESP,EBP
00401019 |? 5D POP EBP
0040101A |? C3 RETN
显然7在文件上的存在形式就是代码区的00401004处这一行指令,即机器语言的 C745 FC 07000 里的 07000000
而temp符号根本不存在,因为一个非静态的局部变量需要符号做什么呢!?
运行时,内存里用来存放7的栈位置就是ebp-4
对temp和7来说,这应该就是全部了,哪里有什么.data,.rdata,.bss ?!
我前面说,局部变量是以代码的方式保存在文件的代码区的,虽然说法很粗糙,但意思没什么错吧?
#41
SS:[EBP-4] 这个是temp的地址,7只是个立即数。
变成汇编之后本来就不存在.rdata或者其他什么,你看不到而已
你可以使用工具查看你的可执行文件,windows下面我不太清楚是什么,pedump应该可以
linux下
objdump
readelf
都可以查看
对这个问题只是个探讨而已,有的我也不是很熟,书刚看到这里,还没完全了然于胸,共同学习。
#42
windows下dumpbin即可
编译/汇编之后,文件里当然存在.rdata(如果有只读数据的话)。加载到内存里也有对应的一个区
只要有初始化的全局/静态数据,编译/汇编后就存放在文件里的.data区。加载到内存里也是对应的一个区
但是,文件里没有局部变量区
不是看不到,是根本没有
另外,ss,ebp,是运行时用于定位局部变量在栈的位置,而不是局部变量在磁盘文件里的位置
#43
有线程栈,我自己理解的“函数栈”是线程栈的子站
线程栈是编译阶段定死的,windows默认貌似4MB,这个在编译选项中可调的
“函数栈”又分两个部分,第一是参数,第二是函数的局部变量。
参数是需要随着函数调用入栈出栈的,一个函数的局部变量也可以看成是一个元素,函数调用的时候入栈
在一个内部来说,每个句柄变量也是在栈上,位置在编译阶段就定好了
线程栈是编译阶段定死的,windows默认貌似4MB,这个在编译选项中可调的
“函数栈”又分两个部分,第一是参数,第二是函数的局部变量。
参数是需要随着函数调用入栈出栈的,一个函数的局部变量也可以看成是一个元素,函数调用的时候入栈
在一个内部来说,每个句柄变量也是在栈上,位置在编译阶段就定好了
#44
#45
没错函数调用的确是是个压栈的过程; 函数返回后,压栈的数据被弹出。
栈的大小,应该由编译器和os共同决定吧。
编译器中,有frame 和 record的概念。来控制函数调用和返回。以及发生异常时,栈的回退(unwinded)等。
函数调用中其相关信息和其局部变量的空间都在栈上分配,而且是编译器自动分配。
至于你说的是sizeof(a) 还是sizeof(b);个人觉得应该大于sizeof(a) + sizeof(b);
因为函数调用的过程,是一个上下文切换的过程,其间,首先要保存现场、然后将函数的返回地址及局部变量压栈、执行函数、返回、恢复现场。 如果写过汇编程序,这个就很容易理解了。
这些都是有编译器实现的,具体细节我们不必关心。
alloc 可以在栈上分配内存。你可以通过在函数内部定义一个足够大数组,来看看,你的编译器分配的stack size。
栈的大小,应该由编译器和os共同决定吧。
编译器中,有frame 和 record的概念。来控制函数调用和返回。以及发生异常时,栈的回退(unwinded)等。
函数调用中其相关信息和其局部变量的空间都在栈上分配,而且是编译器自动分配。
至于你说的是sizeof(a) 还是sizeof(b);个人觉得应该大于sizeof(a) + sizeof(b);
因为函数调用的过程,是一个上下文切换的过程,其间,首先要保存现场、然后将函数的返回地址及局部变量压栈、执行函数、返回、恢复现场。 如果写过汇编程序,这个就很容易理解了。
这些都是有编译器实现的,具体细节我们不必关心。
alloc 可以在栈上分配内存。你可以通过在函数内部定义一个足够大数组,来看看,你的编译器分配的stack size。
#46
哥决定先去学汇编.
#47
栈的大小也可以动态设置吧。CreateThread(...)就可以设置栈的大小
#48
...
简单点 花点时间了解下ebp,esp寄存器的用途,用Ollydbg随便打开一个程序,单步跟踪下函数调用的过程,观察栈的变化 就明白了
简单点 花点时间了解下ebp,esp寄存器的用途,用Ollydbg随便打开一个程序,单步跟踪下函数调用的过程,观察栈的变化 就明白了
#49
"或者, 是我理解有误, 栈的大小可以伸缩?"
这里边还有个栈大小,和实际用了的栈大小。
正是应为编译器不知道程序运行的时候栈有多大,才会有栈溢出这一说,就是实际用的栈空间超过了编译器分配的栈空间,程序就崩溃了。
这里边还有个栈大小,和实际用了的栈大小。
正是应为编译器不知道程序运行的时候栈有多大,才会有栈溢出这一说,就是实际用的栈空间超过了编译器分配的栈空间,程序就崩溃了。
#50
没有所谓的函数栈
线程栈就是栈,只不过是不同的上下文中的术语而已
栈,针对编译原理
线程栈,针对操作系统
现代系统,都是一个线程一个栈,所以叫做线程栈,windows默认一个线程1MB的栈,每新开一个线程,就配套分配1MB的栈,当然也可以指定大小