C언어에 객체가 없듯이 다른 객체지향 패러다임을 채용한 다른 언어들 또한 유저 입장에서 객체를 설계했지 컴퓨터한테 객체를 들먹일 수는 없을 것이다.
C++에서 짤막한 프로그램을 작성해 오브젝트 파일로 컴파일 해보았다.
class MyClass {
private:
int member_int;
public:
MyClass(int m_int)
: member_int{m_int} {};
void increment(){
member_int++;
}
};
int main(void) {
auto c = MyClass{3};
c.increment();
return 0;
}
readelf class.o -a
좋아, 이제 *_*ZN7
MyClass 9
increment Ev
의 내용물을 읽어보자.
objdump class.o -D
내가 봐야할 부분은 과연 increment 라는 함수가 과연 파라미터로 MyClass 포인터를 가지고 가는지다. 왜냐하면 결국 모든 메서드들이 사실 전역함수로 통한다면 멤버들을 어떻게 접근하겠는가? 바로 포인터를 사용할 것이다. 따라서 함부로 단언할 순 없겠지만 컴파일러는 둘 중 한 가지 방법을 사용할 것이다.
파이썬 메서드의 첫번째 파라미터는 반드시 Self
가 들어간다. 그 말은 곧 파이썬도 내부적으로는 메서드를 단순한 함수 취급하는데, 멤버접근을 위한 포인터가 필요했을 뿐이다.
컴퓨터는(정확히는 바이너리는) 클래스에 대해서 아무것도 모르기 때문에 접근지정자 private, public또한 모를 것이다. 그냥 모든 데이터가 전역적으로 덩그러니 놓여있고 클래스나 구조체나 할 것 없이 단지 컴퓨터 입장에선 단지 그 데이터의 위치와 길이, 오프셋에만 관심이 있을 뿐이다.
main
과 MyClass::increment
부분을 조금만 더 뜯어보자.
0000000000000000 <main>:
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: 48 83 ec 10 sub $0x10,%rsp
c: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
13: 00 00
15: 48 89 45 f8 mov %rax,-0x8(%rbp)
19: 31 c0 xor %eax,%eax
1b: 48 8d 45 f4 lea -0xc(%rbp),%rax
1f: be 03 00 00 00 mov $0x3,%esi
24: 48 89 c7 mov %rax,%rdi
27: e8 00 00 00 00 callq 2c <main+0x2c> # 여기가 MyClass 생성자 호출하는 부분인가
2c: 48 8d 45 f4 lea -0xc(%rbp),%rax
30: 48 89 c7 mov %rax,%rdi
33: e8 00 00 00 00 callq 38 <main+0x38> # 여기가 increment 호출하는 부분인가
38: b8 00 00 00 00 mov $0x0,%eax
3d: 48 8b 55 f8 mov -0x8(%rbp),%rdx
41: 64 48 33 14 25 28 00 xor %fs:0x28,%rdx
48: 00 00
4a: 74 05 je 51 <main+0x51>
4c: e8 00 00 00 00 callq 51 <main+0x51>
51: c9 leaveq
52: c3 retq
0000000000000000 <_ZN7MyClass9incrementEv>:
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: 48 89 7d f8 mov %rdi,-0x8(%rbp)
c: 48 8b 45 f8 mov -0x8(%rbp),%rax
10: 8b 00 mov (%rax),%eax
12: 8d 50 01 lea 0x1(%rax),%edx
15: 48 8b 45 f8 mov -0x8(%rbp),%rax
19: 89 10 mov %edx,(%rax)
1b: 90 nop
1c: 5d pop %rbp
1d: c3 retq
Implementing Functions in x86 Assembly
어셈블리에서 함수를 어떻게 다루는지 설명하는 글을 읽고 다시 돌아오자…