2014-05-22 3 views
2
나는 아래로 캐스팅을 이해하려고 노력했다

는 ... 여기에서 파생 된 클래스에 기본 클래스 포인터를 할당 변환하여 ... 내가 시도 여기에서다운 캐스팅 할 때 내부적으로 어떤 일이 발생합니까?

class Shape 
{ 
public: 
    Shape() {} 
    virtual ~Shape() {} 
    virtual void draw(void)  { cout << "Shape: Draw Method" << endl; } 
}; 

class Circle : public Shape 
{ 
public: 
    Circle(){} 
    ~Circle(){} 
    void draw(void)  { cout << "Circle: Draw Method" << endl; } 
    void display(void) { cout << "Circle: Only CIRCLE has this" << endl; } 
}; 

int main(void) 
{ 
    Shape newShape; 
    Circle *ptrCircle1 = (Circle *)&newShape; 
    ptrCircle1->draw(); 
    ptrCircle1->display(); 

    return EXIT_SUCCESS; 
} 

나는 아래로 캐스팅 아래로 가지고있다. 내가 이해하는 것은

Circle* ptrCircle1 --> +------+ new Shape() 
         |draw()| 
         +------+ 

기본 클래스는 파생 통화가있는 display() 방법에 대한 정보가 없습니다 ...입니다. 나는 추락을 예상하고 있었지만, 결과를 출력했다.

누군가 내부적으로 일어날 일을 설명 할 수 있는가?

덕분에 ...

+3

불법입니다. 정의되지 않은 동작이 발생합니다. 불법입니다. – Mat

+2

"정의되지 않은 동작"은 작업이 유효한 결과임을 의미합니다. :) 충돌을 원한다면 멤버 변수를 Circle에 추가하고 display()에서 참조하십시오. 그건 아마 * 할거야,하지만 여전히 보장하지. – dlf

+0

C- 스타일 캐스팅을 사용해서는 안되니? – StackIT

답변

6

는 C 스타일 캐스트, 상속 관계,이 경우 인해, static_cast에 해당합니다. 대부분의 캐스트 (일부 검사가 주입되는 곳인 dynamic_cast 제외)와 마찬가지로 객체가 실제로 Circle이라고 말할 때 컴파일러는 사용자를 신뢰하고 있다고 가정합니다. 문제는 입니다.이 경우 정의되지 않은입니다. 개체는 이 아니기 때문에Circle이 아니므로 컴파일러에게 거짓말을하고 모든 베팅은 꺼져 있습니다.

여기서 실제로 발생하는 현상은 컴파일러가이 조합에 대해 기본에서 파생 형식까지의 오프셋이 있는지 여부를 확인하고 이에 따라 포인터를 조정한다는 것입니다. 이 시점에서 조정 된 주소가있는 파생 된 형식에 대한 포인터를 가져오고 형식 안전은 창을 벗어납니다. 해당 포인터를 통한 액세스는 해당 위치의 메모리가 사용자가 말한 것임을 전제로하며, 메모리를 읽지 않는 것처럼 정의되지 않은 동작으로 해석합니다.

포인터는 언제 조정됩니까?

struct base1 { int x; }; 
struct base2 { int y; }; 
struct derived : base1, base2 {}; 
base2 *p = new derived; 

derived, base1base1::x의 주소는 동일하지만 base2base2::y의 어드레스와 다른. derived에서 base2으로 캐스팅하는 경우 컴파일러는 base2에서 derived으로 캐스팅 할 때 컴파일러가 반대 방향으로 조정할 때 변환에서 포인터를 조정합니다 (주소에 sizeof(base1) 추가).

왜 결과가 나타 납니까?

모양 : 방법

원을 그립니다 만 원이는 컴파일러에 의해 구현되는 방법 동적 파견에 관련이

있습니다. 하나 이상의 가상 함수가있는 각 유형에 대해 컴파일러는 하나 이상의 가상 테이블을 생성합니다. 가상 테이블에는 해당 유형의 각 함수에 대한 최종 재정의 자에 대한 포인터가 들어 있습니다. 모든 객체는 완전한 유형의 가상 테이블에 대한 포인터를 보유합니다. 가상 함수를 호출하는 것은 컴파일러가 테이블에서 룩업을 수행하고 포인터를 따르는 것을 포함합니다.

이 경우 개체는 실제로 Shape이고, vptr은 Shape의 가상 테이블을 참조합니다. Shape에서 Derived으로 전송할 때 컴파일러에게 Circle (그렇지 않은 경우에도)이라고 알립니다. draw()을 호출하면 컴파일러는 vptr을 따르고 (이 경우 Shape 하위 개체의 vptr 및 Circle 하위 개체는 개체의 시작 부분에서 같은 오프셋 (대부분의 ABI에서 0)에있게됩니다. 컴파일러에서 주입 한 호출은 다음과 같습니다. Shape vptr에서은과 (캐스트는 vptr에서 여전히 Shape의 즉, 하지 변화 메모리의 내용을 수행) Shape::draw를 기록했다. 그대로 display() 전화의 경우

동적으로 vptr에서를 통해 전달되지 않습니다 가상 함수가 아닙니다. 즉, 컴파일러가 Circle::draw()에 대한 직접 호출을 사용하여 사용자가 this 포인터로 가지고있는 주소를 전달한다는 것을 의미합니다. 동적 파견 해제하여 가상 기능의 :

ptrCircle1->Circle::draw(); 

이 컴파일러가하는대로 이것은 단지 정의되지 않은 동작되는 표준에 의해, C++ 표준을 탈출 컴파일러 세부 사항의 단지에 대한 설명이라고 기억 괜찮습니다 . 다른 컴파일러는 뭔가 다른 것을 할 수 있습니다 (본인이 본 모든 ABI는 기본적으로 동일합니다).

이러한 것들이 어떻게 작동하는지에 대한 자세한 내용은 을 참조하십시오. Lippman의 C++ 객체 모델 내부에서 살펴볼 수 있습니다. 그것은 다소 오래된 책이지만 컴파일러가 해결해야하는 문제와 컴파일러가 사용한 몇 가지 솔루션을 다룹니다.

2

display()은 가상이 아니므로 대부분의 C++ 구현에서 포인터 값을 사용하지 않습니다. 따라서 정적 주소를 통해 display()으로 전화를 걸었습니다. 그리고 display()는 this을 사용하지 않으므로 작동합니다.

그러나 주석에서 지적했듯이 이는 여전히 정의되지 않은 동작입니다. 다른 컴파일러로 인해 충돌이 발생할 수 있습니다.

nullptr 포인터에서 display()을 호출 할 수도 있습니다. 이는 대부분의 구현에서 동일한 결과를 제공합니다. 하지만 여전히 정의되지 않은 동작입니다.

+0

+1 예, 포인터를 'nullptr'로 만들고 display() 메소드를 호출했습니다. 그것은 작동합니다 !!!. 당신이 말했듯이 그것은 정의되지 않은 행동입니다 !!! – StackIT

관련 문제