나만의 작은 도서관

[C++][Etc] 메모리 재사용 시스템 때문에 댕글링 포인터가 메모리 오염 포인터가 되었던 건에 대해서 본문

C++/문법 및 메소드(STL)

[C++][Etc] 메모리 재사용 시스템 때문에 댕글링 포인터가 메모리 오염 포인터가 되었던 건에 대해서

pledge24 2025. 6. 14. 17:14
주의사항: 해당 글은 다듬지 않은 날것 그대로인 글입니다. 

이슈의 배경: LinkedList 구현

  • 자주 사용하지 않았던 LinkedList의 개념을 확실히 잡고자 STL에서 제공하는 list를 사용하지 않고 이중 연결 리스트(Doubly Linked List)를 직접 구현하고 있던 상황이었다. 새로운 원소를 삽입하기 위해서 Insert 함수를 구현하고 있었는데, 이상하게도 방금 만든 Node가 연결 작업을 하지 않았는데도, 이미 LinkedList에 있는 노드와 연결되어 있던 것이다. Insert 함수의 코드는 아래와 같다.
Node<T>* Insert(Node<T>* cursor, T data){
      Node<T>* newNode = new Node<T>(data); // 여기서 cursor의 prev로 newNode가 연결되어 있음을 확인
      if(empty()){
          newNode->next = end;
          end->prev = newNode;

          begin = newNode;
          return newNode;
      }

      // prevNode <=> newNode
      Node<T>* prevNode = cursor->prev;
      if(prevNode){ 
          prevNode->next = newNode;
          newNode->prev = prevNode;
      }
      else{ // prevNode 없음 -> begin
          begin = newNode;
      }

      // newNode <=> curNode
      Node<T>* curNode = cursor;
      newNode->next = curNode;
      curNode->prev = newNode;

      return newNode;
  }
  • 아무리 생각해도 말이 안 됐다. 방금 new로 생성한 Node가 어떻게 cursor랑 연결이 되어있는 것일까? 멀티 스레드 환경도 아니고, 컴파일러 최적화도 전부 꺼놓은 상태라 추가적인 변수도 없었다. 그래서 원인도 모른 채 코드만 뚫어져라 쳐다보다가 결국 AI한테 물어보았다.

 

이슈의 원인 1: Erase 함수에서의 댕글링 포인터 발생

  • 알고 보니 Insert 함수는 문제가 없었다. 발견한 문제 중 하나는 원소를 삭제하는 Erase함수에 있었는데, 시작 위치에 있는 Node를 삭제할 경우, 오른쪽 Node가 해당 Node를 가리키고 있던 포인터를 nullptr로 안 밀어줘서 댕글링 포인터가 발생하고 있었던 것이었다. 그래서 nullptr로 밀어주는 코드를 한 줄 추가함으로써 댕글링 포인터가 발생하지 않도록 조치하였다.
/* return: cursor 위치에 있는 노드를 지우고, 다음 원소의 위치를 반환 */
// 주의: 원소가 들어있는 위치의 cursor만 들어와야한다.
Node<T>* Erase(Node<T>* cursor){
    Node<T>* prevNode = cursor->prev;
    Node<T>* removeNode = cursor;
    Node<T>* nextNode = cursor->next;
    
    Node<T>* res;
    if(cursor == begin){
        begin = nextNode;
        if(nextNode) nextNode->prev = nullptr;  // 추가된 코드
        res = begin;
    }
    else{
        prevNode->next = nextNode;
        nextNode->prev = prevNode;

        res = nextNode;
    }
   
    delete cursor;

    return res;
}

 

이슈의 원인 2: 메모리 재사용 시스템에 의해 delete 한 메모리가 new 할 때 다시 할당되었다.

  • 하지만 댕글링 포인터가 발생했다는 것만으로는 이슈 발생 원인의 충분한 근거가 되지 못한다. 댕글링 포인터는 사용했을 때 문제가 되는 거지, 사용하지 않은 댕글링 포인터가 방금 new로 생성한 Node를 가리키게 된 것이랑은 별개의 문제이다.
  • 그렇게 찾은 또다른 원인은 바로 new-delete 키워드의 특성과 메모리 재사용 시스템에 있었다. new는 결코 할당하는 메모리를 초기화한 다음에 가져다주지 않는다. 마찬가지로, delete도 메모리를 초기화하지 않는다. 즉, new-delete는 메모리 접근 및 사용 권한만 변경할 뿐, 이전에 존재하던 데이터가 남아있는 것에는 크게 신경 쓰지 않는다는 것이다.
  • 또한, 동적 메모리의 경우 “같은 메모리를 여러 번 재할당한다”는 특성이 있기 때문에 new로 할당받은 메모리를 초기화를 하지 않고 사용하면 안에 이전 데이터가 남아있을 가능성이 높다. 따라서, 아래와 같은 과정으로 이슈가 발생했던 것이다.
    • Erase함수에서 Node를 delete 했지만, 해당 Node를 가리키는 다른 Node의 포인터를 nullptr로 밀어주지 않음(포인터는 댕글링 포인터가 됨)
    • Insert함수에서 새로운 Node를 new로 생성
    • 메모리 재사용 시스템에 의해, 최근에 delete된 Node의 메모리가 재할당됨
    • 댕글링 포인터가 new로 생성된 Node를 가리키게 되면서 댕글링 포인터 → new로 새로 할당한 Node를 가리키는 포인터가 됨.
  • 내가 맞닥뜨린 문제에 포함되지는 않지만, new로 새로 할당한 Node를 가리키는 포인터는 메모리 오염을 발생시킬 수 있는 위험한 포인터로, 포인터 사용으로 인한 메모리 오염이 발생할 수도 있었다.

 

요약

  • delete시 해당 메모리를 가리키는 포인터를 nullptr로 밀어주는 코드가 누락되어 댕글링 포인터가 발생.
  • new로 새로운 메모리를 할당받을때 delete 한 메모리를 재할당
  • 댕글링 포인터는 새로운 메모리를 가리키게 되었고, 댕글링 포인터 → 메모리 오염 포인터로 진화함(나쁜 쪽으로)
  • ⇒ 다음에는 이런 문제가 생기지 않도록 하기 위해 delete 한 메모리를 가리키는 포인터가 있다면, 반드시 까먹지 말고 nullptr로 밀어주자!