문과 코린이의, [C. C++] 기록/C++ 이론

[문과 코린이의 IT 기록장] C,C++ - 가상(virtual)의 원리와 다중상속 1 : 맴버함수와 가상함수의 동작원리 (객체 안에 정말로 맴버함수가 존재하는가?, 가상함수의 동작원리와 가상함수 테이블)

벼리네 2021. 3. 9. 13:49
반응형

[문과 코린이의 IT 기록장] C,C++ - 가상(virtual)의 원리와 다중상속 1 : 맴버함수와 가상함수의 동작원리 

(객체 안에 정말로 맴버함수가 존재하는가?, 가상함수의 동작원리와 가상함수 테이블)




 1. 객체 안에 정말로 맴버함수가 존재하는가?
- 지금까지는 객체 내에 맴버함수가 존재한다고 설명했었다. 그러나 실제로는 객체 외부에 존재한다.



ex ) C언어(구조체 변수와 전역함수)를 이용해, C++(클래스와 객체)를 흉내내보기



ex ) 이 예제를 C언어 스타일로 만들어보자.

#include <iostream>
using namespace std;

// 클래스 Data를 흉내낸 영역
typedef struct Data{ // 함수 포인터 변수가 구조체의 맴버로 등장했다.
int data;
void (*ShowData)(Data*); // ShowData() 함수의 주소값 저장을 위한, 함수 포인터 변수
void (*Add)(Data*, int); // Add() 함수의 주소값 저장을 위한, 함수 포인터 변수
}Data;

void ShowData(Data * THIS){ cout<<"Data : "<<THIS->data<<endl; }
void Add(Data * THIS, int num){ THIS->data += num; }

// 적절히 변경된 main 함수
int main(){

// obj1과 obj2를 객체라고 할 때, 이 두 객체는 ShowData함수와 Add함수를 공유하는 셈이 된다. 

Data obj1 = {15,ShowData,Add};
Data obj2 = {7,ShowData,Add};

 

// 맴버함수 Add()를 호출하는 것처럼 구성한 C코드
obj1.Add(&obj1, 17);
obj2.Add(&obj2, 9);

 

// 맴버함수 ShowData()를 호출하는 것처럼 구성한 C코드
obj1.ShowData(&obj1);
obj2.ShowData(&obj2);

return 0;
}

 


- 위의 내용을 실행하면, 다음과 같은 코드가 된다.

- 이 코드에서 알아야 하는 핵심은, 두 개의 구조체 변수(객체)가 함수를 공유하고 있다는 사실이다.

 

- 실제로 C++에서도 객체와 맴버함수는 이러한 관계를 가진다. 즉, 객체가 생성되면 맴버변수는 객체 내부에 존재하지만, 맴버함수는 메모리의 한 공간에 별도로 위치하고선, 이 함수가 정의된 클래스의 모든 객체가 이를 공유하는 형태를 가진다.





 2. 가상함수의 동작원리와 가상함수 테이블
1) 가상함수의 동작원리와 가상함수 테이블
ex )

- 한 개 이상의 가상함수를 포함하는 클래스에 대해서는 컴파일러가 '가상함수 테이블(V-table)'이라는 것을 만든다.
 * 가상함수 테이블 : 실제 호출되어야 할 함수의 위치정보를 담고 있는 테이블

 

 

#include <iostream>
using namespace std;

class AAA{
private:
 int num1;
public:
 virtual void Func1() { cout<<"Func1"<<endl; }
 virtual void Func2() { cout<<"Func2"<<endl; }
};

[ AAA 클래스의 가상함수 테이블 ]

key value
void AAA :: Func1() 0x 1024번지
void AAA :: Func2() 0x 2048 번지

* key : 호출하고자 하는 함수를 구분지어주는 구분자의 역할
* value : 구분자에 해당하는 함수의 주소정보를 알려주는 역할

** 즉, AAA객체의 Func1() 함수를 호출해야 할 경우, 첫 번째 행의 정보를 참조하여 0x 1024번지에 등록되어 있는 Func1 함수를 호출하게 되는 것

 


class BBB : public AAA{
private:
 int num2;
public:
 virtual void Func1() { cout<<"BBB::Func1"<<endl; }
 void Func3() { cout<<"Func3"<<endl; }
};

 

[ BBB 클래스의 가상함수 테이블 ]

key value
void BBB :: Func1()
// 오버라이딩
0x 3072번지
void AAA :: Func2() 0x 2048번지
void BBB :: Func3() 0x 4096번지

- AAA 클래스의 오버라이딩 된 가상함수 Func1에 대한 정보가 존재하지 않는다.
 * 즉, 오버라이딩 된 가상함수의 주소정보는 유도 클래스의 가상함수 테이블에 포함되지 않는다는 것을 알 수 있다.

 

 

int main(){
AAA * aptr = new AAA();
aptr->Func1();

BBB * bptr = new BBB();
bptr->Func1();

return 0;
}






2) 가상함수 테이블이 참조되는 방식
- 위의 코드가 실행되면, main함수가 실행되기 전, 아래의 형태로 가상함수 테이블이 메모리 공간에 할당된다.

 * 가상함수 테이블은 맴버함수의 호출에 사용되는 일종의 데이터이기 때문에, 객체의 생성과 상관없이 메모리 공간에 할당된다.

 


- 이후, main 함수가 호출되어 객체가 생성되고 나면, 아래의 구조와 참조관계를 구성한다

- 이와 같이 AAA 객체에는 AAA클래스의 가상함수 테이블의 주소 값이 저장되고, BBB객체에는 BBB클래스의 가상함수 테이블의 주소 값이 저장된다. 이는 우리가 실제로 참조하는 것이 아니라, 내부적으로 필요에 의해 참조되는 주소값이다.

 


cf ) 가상함수 테이블에 의한 속도의 저하
- 위에서 설명했듯이, 클래스에 가상함수가 포함되면 가상함수 테이블이 생성되고, 이 테이블을 참조하여 호출될 함수가 결정된다. 이 과정에서 실행속도가 감소된다. 
- 그러나 그 속도의 차이가 매우 적고, 또 이러한 단점에도 불구하고 가상함수는 많은 장점을 제공하기 때문에, 유용하게 활용되고 있다.

 


* 유의사항
- 아직 공부하고 있는 문과생 코린이가, 정리해서 남겨놓은 정리 및 필기노트입니다.
- 정확하지 않거나, 틀린 점이 있을 수 있으니, 유의해서 봐주시면 감사하겠습니다.
- 혹시 잘못된 점을 발견하셨다면, 댓글로 친절하게 남겨주시면 감사하겠습니다 :)
반응형