[C++] InsufficientSpace에러와 메모리 오류

신남·2024년 8월 14일

C++ 언어로 알고리즘 개발하던 도중 만난 InsufficientSpace 이를 해결하기 위해 메모리 관점에서 디버깅 하며 여러 사람의 도움을 받아 시도했으나, 결국 로직 문제였다.....

하지만, 문제를 해결하는 과정에서 알게된 cpp의 메모리 문제에 대해서 공부한 내용을 정리하여 이후 개발에 도움되기 위해 정리하고자 한다.

InsufficientSpace 에러

에러의 원인으로는

  1. 메모리 부족: 프로그램이 실행 중에 동적 메모리를 할당하려고 시도했을 때, 시스템의 가용 메모리가 부족하여 할당에 실패하는 경우. 예를 들어, new 연산자를 사용하여 객체나 배열을 생성할 때 충분한 메모리가 없으면 문제가 발생

  2. 데이터 구조의 크기 초과: 벡터(vector)나 리스트(list)와 같은 동적 데이터 구조를 사용하는 경우, 이 구조체가 가득 찬 상태에서 추가적인 메모리를 할당할 수 없을 때 발생. 이 경우, 추가 공간이 필요하지만 할당할 수 없으면 insufficient space 오류가 발생

  3. 스택 오버플로우(Stack Overflow): 재귀 호출이 너무 깊거나, 큰 지역 변수를 선언하여 스택 메모리를 초과했을 때 오류가 발생, 스택 공간이 부족하다는 의미로 해석

  4. 버퍼 크기 초과: 특정 입력에 대해 할당된 버퍼가 너무 작아서 더 이상 데이터를 넣을 수 없는 경우 발생, 예를 들어, 파일이나 네트워크 데이터 읽기에서 버퍼 크기가 충분하지 않으면 이 오류가 발생

즉, 위의 오류는 메모리가 부족한 상황에서 발생하게 되는데, 메모리 누수나 허상 참조 등의 원인으로 메모리를 소모하면 위 에러가 발생 할 수 있게 된다.

메모리 누수

메모리 누수(memory leak)란? 더이상 사용하지 않는 객체들이 heap영역에 남아있어 불필요하게 메모리를 차지하고 있는 상황이나 heap영역에 할당된 메모리에 대해 접근이 불가능한 상황이 되어 해제가 불가능한 상황을 말한다.

예제 코드

void leak(){
	int *a = new int;
    
    //delete a;
	return;    
}

int main(){
	leak();

	return 0;
}

위 코드를 살펴보자.

  1. main함수에서 leak함수를 호출하여 스택영역에 메모리를 할당한다.
  2. 이어서 new에의해 heap영역에 동적메모리를 할당하고 해당 메모리 주소를 a에 저장한다.
  3. 이후 return에 스택영역에 해당하는 변수 a는 메모리가 해제 되지만, heap영역에 해당하는 메모리는 해제 되지 않았다.
  4. 따라서 main함수에서는 int하나가 heap에서 메모리 할당이 되어있지만, 더이상 사용하지도 않고, 해당 메모리에 접근할 수 있는 방법이 더이상 없어서 필요없는 메모리가 낭비되고 있는 것이다.

위의 코드는 주석으로 처리된 메모리 해제 명령어만 주석 해제 해주면 메모리 누수를 해결 할 수 있다.

또 다른 예제를 보자.

void leak(int * a){
    int *b = new int;
    b = a;
    
    delete b;
    
    return;
}

int main(){
	int *a = new int;
	leak(a);
    
    delete a;
    
	return 0;
}
  1. main함수에서 int메모리를 동적 할당하여 주소를 a에 저장한다.
  2. leak함수를 호출하여 스택영역에 메모리를 할당한다.
  3. 이어서 new에의해 heap영역에 동적메모리를 할당하고 해당 메모리 주소를 b에 저장한다.
  4. b의 메모리 주소가 a로 바뀌었다.
  5. delete과정에서 b의 메모리가 해제 되는데, 이는 main에서 할당한 a의 메모리 주소이다.
  6. return에 의해 b변수가 해제되지만, leak에서 동적할당한 int는 아직 해제되지 않았고, 이로인해 메모리 누수가 발생한다.
  7. 또한, delete a에서 이미 a의 주소는 leak에서 해제되었기에 delete a명령어에서 오류가 발생한다.

위와같이 동적 할당 된 메모리 주소는 함부로 바꾸지 않는 것이 적절하다.

허상 참조

허상 참조(dangling reference) 허상 참조는 유효하지 않은 객체를 가리키고 있는 상태를 말한다.

예제코드

int* dangling(){
    int a = 10;
    
    return &a;
}

int main(){
	int *a = dangling();
    
	return 0;
}

위의 코드의 경우

  1. main에서 dangling()함수를 호출한다.
  2. dangling함수 내에서 a의 변수를 할당한다.
  3. 변수 a의 메모리 주소를 반환했지만, return에의해 a의 메모리가 해제된다.
  4. 스택메모리 내에서만 유효했던 메모리 주소가 main에서 할당되었으므로 main함수에서 a가 보고있는 메모리 주소는 할당되어 있지않은 허상을 참조하고 있는 것이다.

마무리

기존에 파이썬으로 알고리즘문제를 해결하며 메모리나 동적할당 등에 대해서 크게 신경쓰지 않았다. 하지만, cpp로 넘어오면서 동적할당을 제대로 활용하려하니 제대로 이해하지 않은게 문제가 되어 많은 버그를 일으켰다. 이를 계기로 포인터, 메모리 등에 대해서 제대로 이해 할 수 있었다. 이후 프로젝트 등에 적용하는데도 많은 도움이 될 지식이라 기대한다.

  • 현재 hashmap을 직접 구현하는 과정에서 허상참조 문제를 경험하여 정리하고자 한다.
#define MAX_TABLE 5000
struct Hash {
    struct HashNode {
        int id;
        int value;
        HashNode* next_h;

        HashNode(int a, int b, HashNode* c):id(a), value(b), next_h(c) {}
    }*table[MAX_TABLE];



    int get(int mID) {
          HashNode* node = table[mID % MAX_TABLE];
          while (node) {
          if (node->id == mID) {
          return node->value;
          }
          node = node->next_h;
        }

        return -1;
    }

    void set(int mID, int index) {
        int hash = mID % MAX_TABLE;
        HashNode temp = HashNode(mID, index, table[hash]);
        HashNode* new_node = &temp;
        table[hash] = new_node;
    }

    void clear() {
    		for (int i = 0; i < MAX_TABLE; ++i) table[i] = 0;
    }

}hash;

위 객체의 문제점을 뭘까.
바로 set함수
set함수 내에서 선언된 temp 변수는 set함수의 종료와 함께 메모리가 해제된다. 하지만 객체에서 선언된 table는 해제된 temp를 보고 있으므로 허상참조를 하게된다.

허상참조 문제를 해결한 코드는 here

역시 이론만으로 공부하는건 실제 코드 개발할때 기억이 안나는 법이다. 개발하면서 이해해야지

0개의 댓글