我们如何在不弹出堆栈变量的情况下访问堆栈变量? [英] How we access stack variables without poping them?

查看:67
本文介绍了我们如何在不弹出堆栈变量的情况下访问堆栈变量?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

据我所知,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分配给xy.请注意,这些位置在%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.为此,我们首先需要将参数xy推入调用堆栈:

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆