C++ Class 멤버 함수의 묵시적 인자 this

Question: C++ Class 멤버 함수의 묵시적 인자 this는 어떻게 사용되고 있을까?

멤버 함수의 경우 일반 함수와 동일하게 코드 세그먼트 영역에 존재합니다. 다른 일반 함수와의 차이점이라면 실행 패턴이 차이가 있습니다.
멤버 함수는 멤버 함수를 호출할 때 사용한 객체 주소를 묵시적 인자 this로 전달하고 이 this첫 번째 인자 가 됩니다.
그리고 멤버 함수 내에서는 전달된 묵시적 인자 this를 이용하여 멤버에 접근합니다.

한번 this가 진짜로 첫번째 인자이고 암묵적으로 잘 전달되는지 확인해보기로 했습니다.
환경 g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4)

먼저 main함수를 디스어셈 해보았습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0x0000000000400726 <+0>:   push   rbp
0x0000000000400727 <+1>: mov rbp,rsp
0x000000000040072a <+4>: sub rsp,0x10
0x000000000040072e <+8>: mov rax,QWORD PTR fs:0x28
0x0000000000400737 <+17>: mov QWORD PTR [rbp-0x8],rax
0x000000000040073b <+21>: xor eax,eax
0x000000000040073d <+23>: lea rax,[rbp-0x10]
0x0000000000400741 <+27>: mov edx,0x65
0x0000000000400746 <+32>: mov esi,0x64
0x000000000040074b <+37>: mov rdi,rax
0x000000000040074e <+40>: call 0x4007c2 <Point::setxy(int, int)>
0x0000000000400753 <+45>: mov eax,0x0
0x0000000000400758 <+50>: mov rcx,QWORD PTR [rbp-0x8]
0x000000000040075c <+54>: xor rcx,QWORD PTR fs:0x28
0x0000000000400765 <+63>: je 0x40076c <main()+70>
0x0000000000400767 <+65>: call 0x400610 <__stack_chk_fail@plt>
0x000000000040076c <+70>: leave
0x000000000040076d <+71>: ret

x64이기 때문에 calling conventionfastcall방식으로 되어있습니다.

fastcall에선 첫 번째 인자가 rdi, 두 번째 인자가 rsi, 세 번째 인자가 rdx로 넘어갑니다.

이 부분을 자세히 봐봅시다.

1
2
3
4
5
0x000000000040073d <+23>:  lea    rax,[rbp-0x10]
0x0000000000400741 <+27>: mov edx,0x65
0x0000000000400746 <+32>: mov esi,0x64
0x000000000040074b <+37>: mov rdi,rax
0x000000000040074e <+40>: call 0x4007c2 <Point::setxy(int, int)>

lea rax,[rbp-0x10]: raxpoint변수의 주소를 넣고 있습니다. 이것이 this라고 생각됩니다.
mov edx,0x65: edx 즉, 세 번째 인자에 0x65의 값을 넣고 있습니다.
mov esi,0x64: esi 즉, 두 번째 인자에 0x64의 값을 넣고 있습니다.
mov rdi,rax: rax에 담겨있는 point변수의 주소를 rdi즉, 첫 번째 인자에 넣고 있습니다.
call 0x4007c2 <Point::setxy(int, int)>: setxy함수를 호출합니다.

이제 setxy함수를 봐봅시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0x00000000004007c2 <+0>:   push   rbp
0x00000000004007c3 <+1>: mov rbp,rsp
0x00000000004007c6 <+4>: mov QWORD PTR [rbp-0x8],rdi
0x00000000004007ca <+8>: mov DWORD PTR [rbp-0xc],esi
0x00000000004007cd <+11>: mov DWORD PTR [rbp-0x10],edx
0x00000000004007d0 <+14>: mov rax,QWORD PTR [rbp-0x8]
0x00000000004007d4 <+18>: mov edx,DWORD PTR [rbp-0xc]
0x00000000004007d7 <+21>: mov DWORD PTR [rax],edx
0x00000000004007d9 <+23>: mov rax,QWORD PTR [rbp-0x8]
0x00000000004007dd <+27>: mov edx,DWORD PTR [rbp-0x10]
0x00000000004007e0 <+30>: mov DWORD PTR [rax+0x4],edx
0x00000000004007e3 <+33>: nop
0x00000000004007e4 <+34>: pop rbp
0x00000000004007e5 <+35>: ret

[rbp-0xc]0x64를 넣습니다.
[rbp-0x10]0x65를 넣습니다.
raxthis를 넣습니다.

mov DWORD PTR [rax],edx: this로 부터 0만큼 떨어진 변수 m_x0x64를 넣습니다.
mov DWORD PTR [rax+0x4],edx: this로 부터 0x4만큼 떨어진 변수 m_y0x65를 넣습니다.

마치며

이렇게 멤버 함수는 this를 암시적으로 첫 번째 인자에 넣고 호출되는 함수일 뿐 객체에는 포함되지 않습니다.
서로 다른 객체 Point p1, p2를 이용해서 setxy를 호출한다고 해서 p1, p2에 있는 setxy가 호출되는것이 아니라는겁니다.
setxy는 그냥 함수로 존재하며 p1, p2의 주소를 첫 번째 인자로 자동으로 넣고 호출되는 합수인 것입니다.

Share