StatInfo player;
player = CreatePlayer();
lea eax, [ebp-110h] // ebp-110h의 주소를 스택에 push (임시적인 공간?)
push eax
call CreatePlayer (04012FDh)
add esp,4
...
CreatePlayer:
jmp CreatePlayer (0402430h)
StatInfo CreatePlayer()
{
push ebp
mov ebp,esp
sub esp,0D4h
push ebx
push esi
push edi
lea edi,[ebp-0D4h]
mov ecx,35h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
mov ecx,offset _3017A778_CPP_Study@cpp (040F029h)
call @__CheckForDebuggerJustMyCode@4 (040138Eh)
StatInfo ret;
cout << "플레이어 생성" << endl;
...
ret.hp = 100;
mov dword ptr [ret],64h // 디버그 모드에서 친절하게 ret라고 알려주는 것. 원래의 어셈블리 코드를 까보면 ebp-10h으로 되어있을 것이다.
ret.attack = 10;
mov dword ptr [ebp-0Ch],0Ah
ret.defence = 2;
mov dword ptr [ebp-8],2
return ret;
mov eax,dword ptr [ebp+8] // 함수 호출 전 ebp-110h의 주소값을 스택에 push했는데, 현재 스택 프레임에서 ebp+8이 가리키는 곳과 같다.
mov ecx,dword ptr [ret] // ret(ebp-10h)에 있는 값을 ecx에 복사
mov dword ptr [eax],ecx // ecx를 eax가 가리키는 주소에 복사
mov edx,dword ptr [ebp-0Ch] // ebp-0Ch에 있는 값을 edx에 복사
mov dword ptr [eax+4],edx // edx를 eax에 들어있는 주소 + 4에 복사
mov ecx,dword ptr [ebp-8] // ebp-8에 있는 값을 ecx에 복사
mov dword ptr [eax+8],ecx // ecx를 eax에 들어있는 주소 + 8에 복사
mov eax,dword ptr [ebp+8] // 다시 한번 eax에 ebp+8의 주소를 복사
}
...
pop eax
pop edx
pop edi
pop esi
pop ebx
add esp,0D4h
cmp ebp,esp
call __RTC_CheckEsp (0401294h)
mov esp,ebp
pop ebp
ret
...
StatInfo player;
player = CreatePlayer();
lea eax, [ebp-110h]
push eax
call CreatePlayer (04012FDh)
add esp, 4 // esp 복원
mov ecx,dword ptr [eax] // CreatePlayer 반환 전 ebp+8에 들어있는 주소를 eax에 복사했으므로 eax에는 CreatePlayer 호출 전 push했던 ebp-110h의 주소가 들어있다.
mov dword ptr [ebp-0FCh],ecx
mov edx,dword ptr [eax+4]
mov dword ptr [ebp-0F8h],edx
mov eax,dword ptr [eax+8]
mov dword ptr [ebp-0F4h],eax
mov ecx,dword ptr [ebp-0FCh] // 함수 호출 전 임시적인 스택 공간(ebp-110h)의 주소를 전달하여 반환 전 이 주소에 값을 복사하고 주소를 레지스터에 저장한 후 반환하고나서 중간에 다른 스택 공간을 거쳐 최종적으로 player에 복사되는데, 중간에 다른 스택 공간에 먼저 복사하는 이유를 모르겠다.
mov dword ptr [player],ecx
mov edx,dword ptr [ebp-0F8h]
mov dword ptr [ebp-10h],edx
mov eax,dword ptr [ebp-0F4h]
mov dword ptr [ebp-0Ch],eax
스택의 임시적인 주소를 넘겨줌 -> 함수 호출 -> 반환 전에 임시 주소에 값을 복사 및 임시 주소를 레지스터에 저장 -> 반환 후 임시 주소의 값을 중간 스택 공간에 복사 -> 중간 스택 공간에서 player에 복사
임시적인 공간을 마련하여 주소를 넘겨주고, 반환 전 이곳에 복사한다. 반환 후에는 임시적인 공간에서 중간 공간에 복사, 중간 공간에서 player에 복사하는 과정이 이루어진다. 함수 내 지역변수를 할당하고 값을 조작하는 일 외에 임시 공간, 중간 공간, 함수 밖에서 반환받는 공간과 관련하여 세 번의 복사가 이루어진다.
구조체를 return받는 방식은 Release 모드일 때에는 컴파일러 최적화로 코드가 좀 더 개선될 수 있겠지만, 반드시 그렇게 되는 건 아니다.
StatInfo monster;
CreateMonster(&monster);
lea eax,[monster]
push eax
call CreateMonster (0401091h)
add esp,4
CreateMonster:
jmp CreateMonster (0402380h)
void CreateMonster(StatInfo* info)
{
push ebp
mov ebp,esp
sub esp,0C0h
push ebx
push esi
push edi
...
cout << "몬스터 생성" << endl;
...
info->hp = 40;
mov eax,dword ptr [info]
mov dword ptr [eax],28h
info->attack = 8;
mov eax,dword ptr [info]
mov dword ptr [eax+4],8
info->defence = 1;
mov eax,dword ptr [info]
mov dword ptr [eax+8],1
}
pop edi
pop esi
pop ebx
add esp,0C0h
cmp ebp,esp
call __RTC_CheckEsp (0401294h)
mov esp,ebp
pop ebp
ret
StatInfo monster;
CreateMonster(&monster);
lea eax,[monster]
push eax
call CreateMonster (0401091h)
add esp,4
}
주소를 넘겨주는 방식은 주소를 스택에 넣어서 그 주소에 접근하여 바로 원본을 수정하는 일 외에 부가적인 일이 없다.
물론 함수 내부에서 값을 대입하는 부분에서는 주소를 레지스터에 넣은 후 접근해야 해서 스택에서 바로 값을 대입하는 것보다 느릴 수 있지만, 구조체를 세 번이나 복사하는 일은 하지 않을 수 있다.
번외편1)
구조체끼리 복사할 때 무슨 일이 벌어질까?
player = monster;
mov eax,dword ptr [monster]
mov dword ptr [player],eax
mov ecx,dword ptr [ebp-24h]
mov dword ptr [ebp-10h],ecx
mov edx,dword ptr [ebp-20h]
mov dword ptr [ebp-0Ch],edx