Я хотел выяснить, как работают объекты, посмотрев результаты сборки программы. У меня есть класс под названием Numbers
содержащий три 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
}
Я думал, что базовый указатель указывает на вершину стекового фрейма, а поскольку функция является главной, он указывает на начало программы. Как это может быть вычитание из базового указателя, когда указатель стека является то, что указывает на текущий адрес? Кроме того, почему он обращается к переменным не по порядку. Меняется n1
затем вычитает 16 байтов, чтобы добраться до адреса n2
, а затем 12 байтов, чтобы добраться до n3
, Есть ли причина для этого?
Я использую Visual Studio 2013 с MASM в качестве ассемблера.
Между ожиданием медленного запуска Apache Pluto и ожиданием моего босса, чтобы закончить свой звонок, мне нравится бродить по этой пустоши вопросов, отмеченных сборка.
Так что именно в таком скучном настроении я пишу другой, бесполезный ответ на этот и без того довольный ОП.
;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
Регистр ebp обычно указывает на начало начала кадра стека текущей функции (которая обычно содержит указатель на предыдущий кадр) в стеке.
В выводе сборки сначала сохраняется последний указатель кадра стека, затем адрес текущего указателя стека сохраняется в формате ebp. Это начало стекового фрейма для текущей функции. Затем некоторые байты вычитаются из esp, чтобы зарезервировать место в стеке для локальных переменных.
Порядок переменных правильный; адреса переменных находятся ниже текущего адреса ebp (стек увеличивается с высоких адресов на более низкие).
Я думал, что указатель базы указывает на верхнюю часть кадра стека,
и так как функция является главной, начало программы.
main()
на самом деле не первая вызванная функция; Есть много функций запуска libc, вызываемых ранее (например, для инициализации глобального объекта).