[C/C++] C++에서 사용하는 casting 연산자

개발 노트 2009. 1. 5. 17:05 posted by 무병장수권력자


작성자 : 김문규
최초 작성일 : 2009. 1. 6

1. 문제 상황
C 컴파일러는 내맘대로 casting이 가능합니다.
혹자는 장점이라고 합니다만...
한편으로는 개발자의 실수를 그대로 용납한기 때문에 실은 아주 무책임한 컴파일러가 아닐수 없는 것이지요.

1: void main()
2: {
3:     char *str="korea";
4:     int *pi;
5:     char *pc;
6:     pi=(int *)str;
7:     pc=(char *)*pi;
8:     printf("%s\n",pc);
9:     getchar();
10:}

위의 코드에서 문제점을 찾거나 동작을 예측하실 수 있으신가요? 오~ 대단하시네요.
제가 돌려보니 죽습니다. 이유는 무분별한 포인터 캐스팅 때문입니다. line 7에서 문제가 일어납니다.

이유는....
line 6 : pi 는 "korea"를 4바이트를 정수를 가리키는 포인터가 되버립니다.
line 7 : 여기서 실수를 저질렀어요. 정수 값을 포인터로 casting 해버립니다. 결과는 pc는 0x65726f6b 번지를 가리키는 char 포인터가 되버립니다.

그래서, 운좋으면 쓰레기값을 반환하지만 대개의 경우에는 이 프로그램은 죽습니다.

이런 문제는 물론 제가 포인터 casting을 잘못한 문제가 있지요. ^^
하지만, 이를 컴파일러가 귀뜸만 해주었다면....하는 아쉬움이 남습니다.

C++ 컴파일러는 이보다 좀 더 명시적인 캐스팅 방법을 제공하며 이번 포스트에서는 이 내용을 다루어 볼까 합니다.

2. C++ casting
C++에서 추가된 casting 방식의 장점은 개발자가 casting의 목적을 명확하게 명시함으로써 개발자의 의도를 컴파일러가 알게하고, 그 결과 컴파일러는 개발자의 의도와 다른 casting 실수를 개발자에게 알려주는 것입니다

다음과 같은 casting이 존재합니다. 2개의 포스트로 나누어서 간단하게 내용을 파악해 보도록 하겠습니다.

1) static_cast
상속 관계에 있는 포인터 간 변환만 허용하는 casting 연산입니다.

int i; double d;
i = static_cast<int>(d)

이렇게 사용하는 겁니다.

상속 관계에 있는 포인터 간 변환만 허용한다는 점이 기억해야할 점입니다.
char *str="korea";
int *pi;
pi=static_cast<int *>(str);
이거~안됩니다.

2) dynamic_cast
위험 소지가 있는 down casting에 대해서 런타임 시에 casting 에러를 발생시켜주는 연산자 입니다.

downcasting의 경우, 자식을 가리키는 포인터를 부모형의 포인터로 가리키려고 하는 경우에 없는 멤버 또는 기능이 존재할 수 있기 때문에 문제가 될 수 있습니다.
dynamic_cast 연산자는 이처럼 문제가 있는 경우, 런타임 시에 실제 참조하고 있는 객체의 타입을 조사해서 위험한 downcasting이라고 판단되면 NULL 값을 반환하게 됩니다.

다만, 한가지 기억할 사항은 단지 상속 관계가 중요한 것이 아니라 virtual 함수가 존재해야 한다는 것입니다. 그렇지 않을 경우에는 상속관계가 있다하더라도 컴파일 시 에러를 발생합니다.

#include <stdio.h>
class CParent
{
public:
 int age;
 virtual void sayWhoIam() { printf("Parent\n"); };
};
class CChild : public CParent
{
public:
 virtual void sayWhoIam() { printf("Child\n"); };
};
void
main()
{
 CParent *parent = new CParent();
 CParent *p_parent1;
 CChild *child = new CChild();
 CChild *p_child1, *p_child2;
 // up casting
 p_parent1 = dynamic_cast<CParent*>(child);
 printf("address of p_parent1 = %x\n", p_parent1);
// child가 upcasting 된 것을 다시 downcasting : safe
 p_child1 = dynamic_cast<CChild*>(p_parent1);   
 printf("address of p_child1 = %x\n", p_child1);
 // unsafe down casting
 p_child2 = dynamic_cast<CChild*>(parent);
 printf("address of p_child2 = %x\n", p_child2);
}

결과값은 아래와 같습니다.


3) const_cast
const로 선언된 변수의 상수성을 없애고자 할 때 사용하는 연산자 입니다.
const 변수의 값을 바꾸고 싶을 때, 잠시 상수성을 없애고서 해당 값을 변경할 수 있도록 하지요.
상수성만을 없애는 것이 그 기능이기 때문에 int->double 또는 그 반대와 같이 당연히 가능한 형변환 조차도 const_cast를 사용한 경우에는 불가능하다는 사실을 기억해 두세요.

#include <stdio.h>
void main()
{
    char str[]="string";
    const char *c1=str;
    char *c2;
    c2=const_cast<char *>(c1);
    c2[0]='a';
    printf("%s\n",c2);
}


적색으로 표시한 7번째 라인은 물론 C에서 처럼
c2 = (char*)c1
으로 사용할 수 있습니다.
하지만 앞선 경우와 마찬가지로 개발자의 의도를 컴파일러에 분명하게 밝힌다는 것이 그 의미를 가집니다.

4) reinterpret_cast
다시 등장한 제멋대로 casting을 위한 연산자 입니다.
기본 타입 변환을 제외한 모든 변환이 가능합니다. 그렇기 때문에 컴파일러는 해당 연산자에 대해서는 어떠한 에러 또는 주의도 발생시키지 않습니다.
(기본 타입 변환은 static_cast를 사용해야 합니다.)

int *pi;
pi = reinterpret_cast<int*>(0x12345678);

3. 맺음말
C/C++ 프로그래밍을 하다보면, 순간의 편리함을 위해서 무리한 casting을 사용하는 경우가 적지 않습니다. 그 당시에는 그럴만한 이유가 있겠지만 개발이 종료되고 한참의 시간이 흐르면 본인조차도 그렇게 코딩한 이유를 모를때가 많지요.
그렇기에 casting과 같이 모호한 데이터 변환의 경우에는 그 나름의 이유를 명확하게 명시하는 것이 중요할 것입니다. 이런 측면에서 C++의 추가된 casting 연산자의 목적을 이해하는 것이 좋겠습니다.
감사합니다.

4. 참조 자료
http://www.winapi.co.kr/clec/cpp3/33-2-1.htm
http://www.winapi.co.kr/clec/cpp3/33-2-2.htm
http://www.winapi.co.kr/clec/cpp3/33-2-3.htm
http://www.winapi.co.kr/clec/cpp3/33-2-4.htm
http://www.winapi.co.kr/clec/cpp3/33-2-5.htm