为什么当我访问一个由三个整数组成的对象时,它会从基指针而不是堆栈指针中减去?

Why when I access an object consisting of three integers, does it subtract from the base pointer, and not the stack pointer?

我想通过查看程序的汇编输出来弄清楚对象是如何工作的。我有一个名为 Numbers 的 class,其中包含三个 ints.

class Numbers {

public:

    int n1;
    int n2;
    int n3;

};

在主函数中,我创建了一个名为 obj 的实例,并将每个变量设置为一个数字。

int main() {

    Numbers obj;

    obj.n1 = 1;
    obj.n2 = 2;
    obj.n3 = 3;

}

下面的代码是生成的程序集:

int main() {
00935240  push        ebp  
00935241  mov         ebp,esp  
00935243  sub         esp,0D8h  
00935249  push        ebx  
0093524A  push        esi  
0093524B  push        edi  
0093524C  lea         edi,[ebp-0D8h]  
00935252  mov         ecx,36h  
00935257  mov         eax,0CCCCCCCCh  
0093525C  rep stos    dword ptr es:[edi]  
0093525E  mov         eax,dword ptr ds:[0093F000h]  
00935263  xor         eax,ebp  
00935265  mov         dword ptr [ebp-4],eax  

    Numbers obj;

    obj.n1 = 1;
00935268  mov         dword ptr [obj],1       ; === Here ===
    obj.n2 = 2;
0093526F  mov         dword ptr [ebp-10h],2   ; === Here ===
    obj.n3 = 3;
00935276  mov         dword ptr [ebp-0Ch],3   ; === Here ===

    return 0;
0093527D  xor         eax,eax  
}

我以为基指针指向栈帧的顶部,而且由于函数是main,指向程序的开始。当堆栈指针指向当前地址时,它怎么能从基指针中减去呢?另外,为什么它会乱序访问变量。它改变 n1,然后减去 16 个字节得到 n2 的地址,然后减去 12 个字节得到 n3。它这样做有什么理由吗?

我正在使用 Visual Studio 2013,使用 MASM 作为汇编程序。

ebp寄存器通常指向栈上当前函数的栈帧的开始(通常包含指向前一帧的指针)。

在汇编输出中,先保存最后一个栈帧指针,然后在ebp中保存当前栈指针的地址。这是当前函数堆栈帧的开始。然后,从 esp 中减去一些字节以在堆栈上为局部变量保留 space。

变量顺序正确;变量地址低于当前 ebp 地址(堆栈从高地址向低地址增长)。

I thought that the base pointer pointed to the top of the stack frame, and since the function is main, the start of the program.

main() 并不是第一个调用的函数;之前调用了很多libc启动函数(例如全局对象初始化)

在等待缓慢的 Apache Pluto 启动和等待我的老板完成她的电话之间,我喜欢在这些标有 .
的问题荒野中徘徊 所以我就是怀着这种无聊的心情,再写一个没用的答案给这个已经满意的OP。

;PROLOG

push ebp                    ;Save the caller frame pointer
mov ebp, esp                ;Make our frame pointer 

;ALLOCATE SPACE

sub esp, 0D8h               ;Reserve 216 bytes on the stack 
                            ;Why 216? I dunno, maybe this makes the compiler
                            ;source code easy to write/read/mantain

;SAVE CALLER REGS

push ebx 
push esi 
push edi                    ;Save caller register that we must not clobber

;INIT ALLOCATED SPACE

lea edi, [ebp-0D8h]         ;EDI point to the start (the lower limit) of 
                            ;our reserved space (EDI = EBP-0d8h)

mov ecx, 36h                ;ECX is the number of DWORD to write, 
                            ;36h*4 = 0d8h = 216 bytes

mov eax, 0CCCCCCCCh         ;EAX is the DWORD to write, 0cccccccch comes 
                            ;from the fact that: 1) 0cch is the opcode for 
                            ;int 03h which is by convention the debug exception 
                            ;2) it is easy to spot 3) it is an invalid address to
                            ;deference. This way an uninitialized var will misbehave 
                            ;when used (not for arithmetic). This is for debug purpose.

rep stos dword ptr es:[edi] ;Write ECX times EAX from ES:EDI upward (N.B. UPWARD) 

;SET UP THE CANARY

mov eax, dword ptr ds:[0093F000h]   ;Take a value which is safe in memory and cannot be
                                    ;overwritten by stack overflow (those guys, grrrr...)

xor eax, ebp                        ;Compute a function of the frame pointer and the canary
                                    ;This can make the canary unique on every invocation.
                                    ;The function is a xor 

mov dword ptr [ebp-4],eax           ;The canary is at the very beginning (ending?) or our 
                                    ;allocated space. It is just below the frame pointer.


;Set the object fields                                  

mov dword ptr [obj], 1              ;I believe this obj is [ebp-14h]
mov dword ptr [ebp-10h], 2          ;Remember that [ebp-10h] is after [ebp-14h], just like
mov dword ptr [ebp-0Ch], 3          ;-10 is after (i.e. bigger than)  -14. 


;Return the value 0

xor eax,eax                         ;EAX have to hold the returned value at the end of the 
                                    ;function, V XOR V = 0 for all V