포인터와 참조자 이야기

Posted by 엘키의 주절 주절 on March 29, 2009

자~ 간만에 C++ 이야기입니다~! C언어의 난이도를 몇배로 높여준! 많은 사람들이 C언어를 포기하게 만든 원흉! 포인터부터 얘기해보죠.

포인터(*)란 아시다시피, 메모리 주소를 가리키는 변수입니다. 가리키는 곳은 언제든 변할 수 있고, 아무곳도 가리키지 않는 상태(NULL 포인터)도 있습니다.

참조자(&)란 뭘까요? 참조자는 변수의 다른 이름입니다. 포인터와 비슷하죠? 포인터와의 차이점은 참조자는 아무것도 가리키지 않는 상태가 없다는 것입니다.

포인터가 어려운 이유는 사용법 자체가 아니라 관리가 어렵기 때문입니다.

다음 코드를 살펴 보시죠. 포인터로 인해 발생할 수 있는 몇가지 상황을 보여줍니다.

1
2
3
4
5
6
7
8
9
    int *pDynamic= new int;
    int *ptr= pDynamic;
    delete pDynamic;
    pDynamic= NULL; //1. 주인 잃은 포인터 : 위 코드가 실행된 이후  piPtr가 가리키는 곳은 해제된 메모리가 됐기에 주인 잃은 포인터가됨.

    *pDynamic= 6;  //2: NULL포인터 참조 : NULL포인터에값대입해서오류발생 

    if(ptr)
        delete ptr; //3: 포인터 delete 두번 : pDynamic 이 delete되면서 한번 delete 된 곳을 또 delete 했기에문제발생.

이 처럼 포인터 사용시에는 늘 주의깊게 신경써야만 합니다. 포인터로 인해 발생하는 문제들은 대부분 끔찍한 결과를 낳기 때문입니다.

그렇다면… C++에서 야심차게 도입한 참조자에는 이런 문제가 없을까요?

그렇진 않았습니다. 참조자 역시 포인터와 마찬가지로 자신이 가리키던 객체보다, 참조자가 수명이 길면 안됩니다.

아래 코드를 보시면, 참조자의 실체를 파악하실 수 있을 겁니다.

1
2
3
4
5
6
    std::queue q;
    q.push(1); //queue는 push시에 내부적으로 동적할당 함
    int & n = q.front(); //동적할당된 1이 저장된 장소의 참조자를 리턴
    q.pop(); //pop시에는 동적할당을 해제함. 현재 n은 1이 저장되었었지만 지금은 해제된 주소를 가리킴. 
    q.push(2); //싱글 스레드이기에, 1이 저장되어있었던 주소를 동적 할당받아서 2를 대입.
    printf("%d\n", n); //n은 실제로는 포인터이기에 1이 저장되었던 주소를 참조해 2를 출력함

어떤가요? 참조자도 가리키는 대상이 사라진다면 (현재의 예는 동적할당되는 객체였지만, 임시 변수도 마찬가지), 주인 잃은 참조자가 될 수 있습니다.

그렇지만 참조자는 위 문제만 주의한다면, 포인터처럼 NULL검사를 할 필요가 없습니다. 그리고 포인터처럼 니모닉 (별칭) 기능을 충실히 해주죠.

참조자를 사용할 수 있는 상황이라고 판단이 되면, 포인터보다 참조자를 사용하는 것이 좋습니다.

정리하자면, 참조자는 변수의 별칭이기에 특정 변수의 다른 이름처럼 사용 가능합니다. NULL (아무것도 가리키지 않는) 상태가 없다면 참조자를, NULL 상태가 있거나 동적할당된 데이터를 관리하려면 포인터를 써야합니다.

또한, 포인터나 참조자 모두 자신이 가리키는 대상이 사라질 때를 주의해야 한다는 점에 늘 유념해서 코딩 하셔야 합니다.

포인터와 참조자에 대해서 썰을 늘어놨는데요, 헷갈리시는 분들에게 도움이 되셨길 바라며 이만 줄입니다. (__) 꾸벅~