我们如何在不弹出堆栈变量的情况下访问堆栈变量? [英] How we access stack variables without poping them?
问题描述
据我所知,C语言中有两种变量,堆栈变量和堆变量.堆栈变量是快速的,并由编译器和CPU自动管理.我有关堆栈变量的问题是这些:
As far as i know there are two kind of variables in C, stack variables and heap variables. Stack variables are fast and managed by compiler and CPU automatically. My question about stack variables are these:
- 堆栈变量是否真的存储在堆栈FILO数据结构中?
- 如果是这样,为什么我们可以使用它们而不弹出它们并失去它们的值?
- 为什么使用堆栈来存储它们?出队或列表出了什么问题?
推荐答案
作为程序员,您不必担心将变量压入或弹出堆栈.生成的机器代码将为您处理所有这些.每次调用函数时,程序都会将项目压入硬件堆栈.其中一些项目是您传递给函数的数据,但大多数是当前程序状态(寄存器值,返回地址等),因此当函数返回时,程序可以在正确的位置继续执行.
You, as the programmer, don't worry about pushing or popping variables on the stack; the generated machine code handles all that for you. Every time you call a function, the program pushes items onto the hardware stack. Some of these items are data you pass to the function, but most of it is the current program state (register values, return addresses, etc.), such that the program can continue executing at the right place when the function returns.
一个例子可能会有所帮助.使用以下简单的C程序:
An example may help. Take the following simple C program:
#include <stdio.h>
int afunc( int a, int b )
{
int c = a * b;
return c;
}
int main( void )
{
int x;
int y;
int z;
x = 2;
y = 3;
z = afunc( x, y );
printf( "z = %d\n", z );
return 0;
}
使用gcc 2.96在老化的Red Hat盒子上进行编译,如下所示:
Compiling it with gcc 2.96 on an aging Red Hat box as follows:
gcc -o demo -g -std=c99 -pedantic -Wall -Werror -Wa,-aldh=demo.lst demo.c
为我提供了以下输出清单:
gives me the following output listing:
1 .file "demo.c"
2 .version "01.01"
5 .text
6 .Ltext0:
165 .align 4
169 .globl afunc
171 afunc:
1:demo.c **** #include <stdio.h>
2:demo.c ****
3:demo.c **** int afunc( int a, int b )
4:demo.c **** {
173 .LM1:
174 .LBB2:
175 0000 55 pushl %ebp
176 0001 89E5 movl %esp, %ebp
177 0003 83EC04 subl $4, %esp
5:demo.c **** int c = a * b;
179 .LM2:
180 0006 8B4508 movl 8(%ebp), %eax
181 0009 0FAF450C imull 12(%ebp), %eax
182 000d 8945FC movl %eax, -4(%ebp)
6:demo.c **** return c;
184 .LM3:
185 0010 8B45FC movl -4(%ebp), %eax
186 0013 89C0 movl %eax, %eax
7:demo.c **** }
188 .LM4:
189 .LBE2:
190 0015 C9 leave
191 0016 C3 ret
192 .Lfe1:
197 .Lscope0:
199 .section .rodata
200 .LC0:
201 0000 7A203D20 .string "z = %d\n"
201 25640A00
202 .text
203 0017 90 .align 4
205 .globl main
207 main:
8:demo.c ****
9:demo.c **** int main( void )
10:demo.c **** {
209 .LM5:
210 .LBB3:
211 0018 55 pushl %ebp
212 0019 89E5 movl %esp, %ebp
213 001b 83EC18 subl $24, %esp
11:demo.c **** int x;
12:demo.c **** int y;
13:demo.c **** int z;
14:demo.c ****
15:demo.c **** x = 2;
215 .LM6:
216 001e C745FC02 movl $2, -4(%ebp)
216 000000
16:demo.c **** y = 3;
218 .LM7:
219 0025 C745F803 movl $3, -8(%ebp)
219 000000
17:demo.c ****
18:demo.c **** z = afunc( x, y );
221 .LM8:
222 002c 83EC08 subl $8, %esp
223 002f FF75F8 pushl -8(%ebp)
224 0032 FF75FC pushl -4(%ebp)
225 0035 E8FCFFFF call afunc
225 FF
226 003a 83C410 addl $16, %esp
227 003d 89C0 movl %eax, %eax
228 003f 8945F4 movl %eax, -12(%ebp)
19:demo.c **** printf( "z = %d\n", z );
230 .LM9:
231 0042 83EC08 subl $8, %esp
232 0045 FF75F4 pushl -12(%ebp)
233 0048 68000000 pushl $.LC0
233 00
234 004d E8FCFFFF call printf
234 FF
235 0052 83C410 addl $16, %esp
20:demo.c ****
21:demo.c **** return 0;
237 .LM10:
238 0055 B8000000 movl $0, %eax
238 00
22:demo.c **** }
240 .LM11:
241 .LBE3:
242 005a C9 leave
243 005b C3 ret
244 .Lfe2:
251 .Lscope1:
253 .text
255 .Letext:
256 .ident "GCC: (GNU) 2.96 20000731 (Red Hat Linux 7.2 2.96-112.7.2)"
因此,从main
开始,我们有以下几行:
So, starting with main
, we have the following lines:
211 0018 55 pushl %ebp
212 0019 89E5 movl %esp, %ebp
213 001b 83EC18 subl $24, %esp
%esp
指向堆栈的顶部; %ebp
指向堆栈框架,位于局部变量和函数参数之间.这些行通过将%ebp
的当前值压入堆栈,然后将当前堆栈顶部的位置写入%ebp
,然后将%esp
前进24个字节(堆栈向下"增长,或减少地址(在x86上).在我的系统上的调试器中逐步执行该程序,我们看到堆栈设置如下 1 :
%esp
points to the top of the stack; %ebp
points into the stack frame, between the local variables and function arguments. These lines save the current value of %ebp
by pushing it onto the stack, then write the location of the current top of the stack to %ebp
, and then advance %esp
by 24 bytes (the stack grows "down", or towards decreasing addresses, on x86). Stepping through the execution of this program in a debugger on my system, we see the stack is set up as follows1:
Address 0x00 0x01 0x02 0x03
------- ---- ---- ---- ----
0xbfffd9d8 0xbf 0xff 0xda 0x18 <-- %ebp, 0xbfffda18 is the previous value
0xbfffd9d4 0x08 0x04 0x96 0x40 <-- x
0xbfffd9d0 0x08 0x04 0x95 0x40 <-- y
0xbfffd9cc 0x08 0x04 0x84 0x41 <-- z
0xbfffd9c8 0xbf 0xff 0xd9 0xe8
0xbfffd9c4 0xbf 0xff 0xda 0x44
0xbfffd9c0 0x40 0x01 0x5e 0x2c <-- %esp
那我们就行了
216 001e C745FC02 movl $2, -4(%ebp)
和
219 0025 C745F803 movl $3, -8(%ebp)
,分别将2和3分配给x
和y
.请注意,这些位置在%ebp
中称为偏移量.所以现在我们的堆栈看起来像这样:
which assign 2 and 3 to x
and y
, respectively. Note that these locations are referred to as offsets from %ebp
. So now our stack looks like this:
Address 0x00 0x01 0x02 0x03
------- ---- ---- ---- ----
0xbfffd9d8 0xbf 0xff 0xda 0x18 <-- %ebp
0xbfffd9d4 0x00 0x00 0x00 0x02 <-- x
0xbfffd9d0 0x00 0x00 0x00 0x03 <-- y
0xbfffd9cc 0x08 0x04 0x84 0x41 <-- z
0xbfffd9c8 0xbf 0xff 0xd9 0xe8
0xbfffd9c4 0xbf 0xff 0xda 0x44
0xbfffd9c0 0x40 0x01 0x5e 0x2c <-- %esp
现在我们叫afunc
.为此,我们首先需要将参数x
和y
推入调用堆栈:
Now we call afunc
. To do that, we first need to push the arguments x
and y
on the call stack:
222 002c 83EC08 subl $8, %esp
223 002f FF75F8 pushl -8(%ebp)
224 0032 FF75FC pushl -4(%ebp)
所以现在我们的堆栈看起来像
So now our stack looks like
Address 0x00 0x01 0x02 0x03
------- ---- ---- ---- ----
0xbfffd9d8 0xbf 0xff 0xda 0x18 <-- %ebp
0xbfffd9d4 0x00 0x00 0x00 0x02 <-- x
0xbfffd9d0 0x00 0x00 0x00 0x03 <-- y
0xbfffd9cc 0x08 0x04 0x84 0x41 <-- z
0xbfffd9c8 0xbf 0xff 0xd9 0xe8
0xbfffd9c4 0xbf 0xff 0xda 0x44
0xbfffd9c0 0x40 0x01 0x5e 0x2c
0xbfffd9bc 0x40 0x14 0xd7 0xf0
0xbfffd9b8 0x40 0x14 0xe8 0x38
0xbfffd9b4 0x00 0x00 0x00 0x03 <-- b
0xbfffd9b0 0x00 0x00 0x00 0x02 <-- a, %esp
现在我们叫afunc
.我们要做的第一件事是保存%ebp
的当前值,然后再次调整我们的寄存器:
Now we call afunc
. First thing we do is save the current value of %ebp
, then adjust our registers again:
175 0000 55 pushl %ebp
176 0001 89E5 movl %esp, %ebp
177 0003 83EC04 subl $4, %esp
离开我们
Address 0x00 0x01 0x02 0x03
------- ---- ---- ---- ----
0xbfffd9d8 0xbf 0xff 0xda 0x18
0xbfffd9d4 0x00 0x00 0x00 0x02 <-- x
0xbfffd9d0 0x00 0x00 0x00 0x03 <-- y
0xbfffd9cc 0x08 0x04 0x84 0x41 <-- z
0xbfffd9c8 0xbf 0xff 0xd9 0xe8
0xbfffd9c4 0xbf 0xff 0xda 0x44
0xbfffd9c0 0x40 0x01 0x5e 0x2c
0xbfffd9bc 0x40 0x14 0xd7 0xf0
0xbfffd9b8 0x40 0x14 0xe8 0x38
0xbfffd9b4 0x00 0x00 0x00 0x03 <-- b
0xbfffd9b0 0x00 0x00 0x00 0x02 <-- a
0xbfffd9ac 0x08 0x04 0x84 0x9a
0xbfffd9a8 0xbf 0xff 0xd9 0xd8 <-- %ebp
0xbfffd9a4 0x40 0x14 0xd7 0xf0 <-- c, %esp
现在,我们在afunc
中执行计算:
Now we perform our computation in afunc
:
180 0006 8B4508 movl 8(%ebp), %eax
181 0009 0FAF450C imull 12(%ebp), %eax
182 000d 8945FC movl %eax, -4(%ebp)
请注意相对于%ebp
的偏移量:这次它们是正的(函数参数存储在"%ebp
"下方,而本地变量存储在"c2>上方").然后将结果存储在c
:
Note the offsets relative to %ebp
: this time they're positive (function arguments are stored "below" %ebp
, where local variables are stored "above" it). The result is then stored in c
:
Address 0x00 0x01 0x02 0x03
------- ---- ---- ---- ----
0xbfffd9d8 0xbf 0xff 0xda 0x18
0xbfffd9d4 0x00 0x00 0x00 0x02 <-- x
0xbfffd9d0 0x00 0x00 0x00 0x03 <-- y
0xbfffd9cc 0x08 0x04 0x84 0x41 <-- z
0xbfffd9c8 0xbf 0xff 0xd9 0xe8
0xbfffd9c4 0xbf 0xff 0xda 0x44
0xbfffd9c0 0x40 0x01 0x5e 0x2c
0xbfffd9bc 0x40 0x14 0xd7 0xf0
0xbfffd9b8 0x40 0x14 0xe8 0x38
0xbfffd9b4 0x00 0x00 0x00 0x03 <-- b
0xbfffd9b0 0x00 0x00 0x00 0x02 <-- a
0xbfffd9ac 0x08 0x04 0x84 0x9a
0xbfffd9a8 0xbf 0xff 0xd9 0xd8 <-- %ebp
0xbfffd9a4 0x00 0x00 0x00 0x06 <-- c, %esp
函数返回值存储在寄存器%eax
中.现在我们从函数返回:
Function return values are stored in the register %eax
. Now we return from the function:
185 0010 8B45FC movl -4(%ebp), %eax
186 0013 89C0 movl %eax, %eax
190 0015 C9 leave
191 0016 C3 ret
从函数返回时,我们将所有内容从堆栈中弹出回到%esp
所指向的位置,然后再输入afunc
(那里有些魔术,但要认识到%ebp
所指向的地址包含旧的%ebp
的值):
When we return from the function, we pop everything off the stack back to where %esp
was pointing before we entered afunc
(there's some magic there, but recognize that %ebp
was pointing to an address containing the old value of %ebp
):
Address 0x00 0x01 0x02 0x03
------- ---- ---- ---- ----
0xbfffd9d8 0xbf 0xff 0xda 0x18 <-- %ebp
0xbfffd9d4 0x00 0x00 0x00 0x02 <-- x
0xbfffd9d0 0x00 0x00 0x00 0x03 <-- y
0xbfffd9cc 0x08 0x04 0x84 0x41 <-- z
0xbfffd9c8 0xbf 0xff 0xd9 0xe8
0xbfffd9c4 0xbf 0xff 0xda 0x44
0xbfffd9c0 0x40 0x01 0x5e 0x2c <-- %esp
现在我们将结果保存到z
:
And now we save the result to z
:
228 003f 8945F4 movl %eax, -12(%ebp)
给我们留下:
Address 0x00 0x01 0x02 0x03
------- ---- ---- ---- ----
0xbfffd9d8 0xbf 0xff 0xda 0x18 <-- %ebp
0xbfffd9d4 0x00 0x00 0x00 0x02 <-- x
0xbfffd9d0 0x00 0x00 0x00 0x03 <-- y
0xbfffd9cc 0x00 0x00 0x00 0x06 <-- z
0xbfffd9c8 0xbf 0xff 0xd9 0xe8
0xbfffd9c4 0xbf 0xff 0xda 0x44
0xbfffd9c0 0x40 0x01 0x5e 0x2c <-- %esp
请注意,这就是特定的硬件/软件组合和特定的编译器的外观;细节在编译器之间会有所不同(最新版本的gcc使用寄存器在任何可能的地方传递函数参数,而不是将其压入堆栈),但是一般概念是相同的.只是不要以为这是事情的完成方式.
Note that this is how things look on a particular hardware/software combination and a particular compiler; the details will vary between compilers (the latest version of gcc uses registers to pass function arguments anywhere it can, rather than pushing them on to the stack), but the general concepts will be the same. Just don't assume this is the way things are done.
1.存储在0xbfffd9c4和0xbfffd9c8之间的值(最有可能)与我们的代码无关;它们只是在其他操作中使用了这些存储位置之后剩下的位模式.我认为编译器假定设置本地框架的空间最小.
这篇关于我们如何在不弹出堆栈变量的情况下访问堆栈变量?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!