티스토리 뷰

&(참조 포인터)를 까먹고 빼먹어서 삽질하여서, 또 저지를까봐 써야겠다.


예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
 
class c1 {
public:
    virtual void test() {
        std::cout << "[object] c1" << std::endl;
    }
};
 
class c2 :public c1 {
public:
    void test() {
        std::cout << "[object] c2" << std::endl;
    }
};
 
 
int main(void) {
    c1 t1 = *(new c2());
    c1* t2 = new c2();
    c1& t3 = *(new c2());
 
    std::cout << "[+] t1.test()" << std::endl;    
    t1.test();
    std::cout << "[+] t2->test()" << std::endl;
    t2->test();
    std::cout << "[+] t3.test()" << std::endl;
    t3.test();    
    
    return 0;
}
cs


  간단히 코드를 설명하자면, 클래스 c1를 선언해서 멤버 함수 test를 가상 함수로 선언하였다. 그리고 클래스 c2에서는 c1을 상속받고, 가상 함수 test를 오버라이딩하여 사용한다.

  main에는 총 3가지의 케이스를 준비하였다. t1은 내가 실수해서 삽질의 원인을 만든 일반 객체, t2는 포인터형 객체, t3는 참조 포인터형으로 사용하는 객체이다.


출력결과


원인

  객체를 직접 확인해보면 쉽게 알 수 있다. 각 객체의 __vfptr(virtual function pointer)을 보도록 하자. 

  t1은 vfptr에 c1::test()가 저장되어 있고, t2와 t3는 c2::test()가 저장되어 있는 것을 확인할 수 있다. 따라서 출력결과가 위의 출력결과처럼 나오게 되는데, 왜 그런지 정리해 보도록 하자.


  c1 t1 = *(new c2()); 은 먼저, 스택에 클래스 c1에 대한 t1 객체를 생성한다. c2 객체가 아니라 c1 객체를 생성했기 때문에 vfptr은 c1::test()가 된다. 여기서 클래스 c2에 대한 객체를 생성해서 t1과 = 연산을 하는데, = 연산자에 대한 연산자 오버라이딩이 정의되지 않았기 때문에 양 클래스간의 얕은 복사를 수행한다. 하지만 현재의 예제 클레스에는 복사를 수행할 멤버 변수가 존재하지 않기 때문에 복사할 필요가 없어서 불필요한 구문인 c2 객체의 할당은 수행하지 않는다. 현재 중요한 vfptr은 복사를 수행하지 않으므로, c1::test()를 유지한다.


  c1* t2 = new c2(); 는 클래스 c2에 대한 객체를 힙 영역에 생성하고, 이를 스택에 있는 변수 t2에 저장한다. 실제로 c2에 대한 객체를 생성했기 때문에 vfptr은 c2::test()가 된다. t2는 힙 영역에 있는 객체를 가리키고 있기 때문에 객체를 찾아가 사용한다.


  c1& t3 = *(new c2()); 는 클래스 c2에 대한 객체를 힙 영역에 생성하고, t3를 통해 참조해서 사용하도록 한다. 이 또한 역시, 실제로는 c2에 대한 객체가 생성되기 때문에 vfptr은 c2::test()가 된다. 이 후, t3는 힙 영역에 있는 객체를 직접 참조하여 사용한다.


  3개의 예제를 통해 알 수 있는 것은 vfptr에 저장되는 함수는 객체 생성 시점을 따라간다는 점이다. vfptr이 별도의 상황에 의해 덮이지 않는 한, 당연히 객체 생성 시점의 함수를 유지하고 있어야 하기 때문에 그렇다.


결론

  가상 함수를 생성해서 사용하는데, 원하는 결과가 안 나올 때는 실제 객체가 생성되는 시점을 확인하고 *나 & 같은 연산자 들을 잘 체크해보도록 하자. 이것도 문제없다면 유지하고 있어야 할 vfptr이 덮혔다는 것인데, 어디서 덮는지를 체크(이런건 치트엔진이 유용)해야 할 것이다. 개발 삽질이므로 vfptr overwrite mitigation에 대해서는 생략...


'Programming > C' 카테고리의 다른 글

Windows cl.exe option  (0) 2018.03.21
SSL Programming Tutorial  (2) 2018.03.18
소프트웨어 디자인 패턴  (0) 2018.01.27
C++ 파일 내용 정규식 매칭  (0) 2017.09.06
C++ 깔끔한 패턴 및 코딩 연습 백업  (0) 2017.08.30
댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31