동적 메모리 할당

yu-podong·2021년 4월 6일
0

CPP

목록 보기
5/9
post-thumbnail

동적 메모리


동적 메모리 : 프로그램 실행 중에 메모리의 할당과 해제가 결정되는 메모리
동적 메모리를 사용하게되면 크게 2가지 장점이 있다.

  1. 메모리 낭비 해결
    • 실행 중, 꼭 필요한 만큼의 메모리만 할당 받아 사용
    • 원하는 크기만큼 할당받는 것이 가능
  2. 메모리의 할당 & 해제 시점을 프로그래머가 제어 가능

정적 할당 & 동적 할당

정적 할당 : 변수 선언을 통해 필요한 메모리 할당
동적 할당 : 필요한 양이 예측되지 않는 경우에 사용되며, 실행 중에 OS로부터 할당
                -> heap으로부터 할당된다.

heap : 운영체제가 소유하고 관리하는 메모리로, 모든 프로세스가 공유할 수 있는 메모리이다.

특징정적 메모리동적 메모리
메모리 할당compile time에 이루어짐runtime에 이루어짐
new 연산자 사용
메모리 해제자동으로 해제명시적으로 해제해야 함
delete 연산자 사용
사용범위지역변수 - 선언된 블록 내
전역변수 - 프로그램 전체
프로그래머가 원하는 동안만큼
메모리 관리 책임컴파일러 책임프로그래머의 책임

new / delete 연산자

new연산자

변수 하나를 동적으로 할당하는 C++ 연산자이고, 메모리 할당에 실패할 시, null 포인터 리턴한다.
만약, 할당된 메모리를 초기화하고 싶으면, ()를 사용하면 된다.

int *p = NULL;
int p2 = new int(10);
p = new int;

//메모리 할당이 제대로 이루어지지 않았을 때
if(p == NULL) {
	//에러 처리
}

동적메모리 할당 과정
1. 메모리에 해당 size만큼 할당
2. 할당한 메모리 주소를 return
3. 주소를 받는 변수 = pointer 변수
4. pointer 변수에 저장된 값을 이용하여 data 활용

delete 연산자

할당받은 변수 하나에 해당하는 동적 메모리를 해제하는 연산자이다.
반드시, 동적 메모리 사용이 끝나면 delete 연산자로 해제해야 한다.

이때, delete연산자를 사용하면 포인터 변수가 사라지는 것이 아니고 포인터 변수에 들어있던 주소와 연결을 끊는 것이다. 그러니, 동적 메모리를 해제한 다음, 해당 포인터 변수를 null로 지정하는 것이 안전하다.

Q. delete연산자를 사용하지 않고, 다른 주소를 가지게 되면 어떻게 될까?
A. 기존에 new를 이용하여 할당했던 동적 메모리 공간은 그대로 살아있게 된다.
    그러나 해당 메모리 공간은 다시 접근할 수 없고, 다른 변수도 해당 공간은 사용할 수 없다.

int main() {
	int *p = NULL;
    	p = new int(0);
        
        if (p == NULL) {
        	cout << "동적 메모리 할당 실패";
            return 1;
        }
        cout << p[0];	//== *p
        delete p;	//동적 메모리 해제
        p=NULL;		//안전하게 NULL로 초기화
        
	return 0;
}

동적 메모리 배열의 할당 및 반환


new[], delete[]

동적 메모리 배열을 할당하고 해제하는 연산자
위의 연산자와 동일하게 동적 메모리 배열을 할당할 때는 new[] 연산자, 해제할 때는 delete[]연산자를 사용한다.

int num = 5;
int* arr = new int[num];	//int형 size를 5개 갖는 동적 메모리 주소 반환

for(int i=0;i<num;i++) 
	cin >> arr[i];
delete[] arr;

활용 예시 - 문자열 뒤집기

char* reverseString(const char* src, int len) {
	char* reverse = new char[len+1];
    
    for(int i=0;i<len;i++)
    	reverse[i] = src[len-i-1];	//src의 가장 마지막자리부터 가져와 저장
        
    reverse[len] = '\n';
    return reverse;
}

int main() {
	char original[] = "yupodong";
    	char* copy = reverseString(original, 8);
        
        cout << original;
        cout << copy;
        
        delete[] copy;	//Q. copy를 delete연산자를 하는 이유는?
        		//A. copy는 동적 메모리를 받은 reverse을 받았기 때문에
                	//   -> 즉, reverse에게 할당된 동적 메모리 주소를 가지고 있음
        
        return 0;
}

동적 메모리 할당 & 해제 정리


동적 메모리 초기화

동적 할당 시 초기화

데이터타입 *포인터변수 = new 데이터타입(초깃값);

int *pInt = new int(20);
char *pChar = new char('a');

하지만, 배열은 동적 할당 시, 초기화를 진행할 수 없다.

//두 문장 모두 컴파일 error 발생
int *pArr = new int[10](20);
int *pArr2 = new char[20]('a');

동적 메모리 배열 delete시 [] 생략

컴파일 error는 발생하지 않지만, 비정상적인 반환이 진행된다.
항상, new <-> delete / new[] <-> delete[] 이 짝이다.

int *p = new int[10];
delete p;	//비정상 반환, 'delete[] p;'로 작성해야 함

int *q = new int;
delete[] q;	//비정상 반환, 'delete q;'로 작성해야 함

동적 메모리 할당 규칙

  1. 항상 new, delete와 new[], delete[] 쌍을 맞춰서 사용할 것
  2. NULL포인터를 해제하는 것은 안전
    delete, delete[] 연산자는 NULL이 넘겨져 온 경우, 아무 일도 하지 않음 (알아서 처리함)
  3. 적절치 못한 포인터로 delete를 진행하면 runtime error 발생
    • 동적으로 할당 받지 않는 메모리 반환
    • 동일한 메모리 두 번 반환
/* First Case*/
int number = 10;
int *p = &number;
delete p;	//해당 포인터가 가리키는 메모리는 동적으로 할당받은 것이 아님

/* Second Case*/
int *p = new int(10);
delete p;	//정상적인 메모리 반환
delete p;	//이미 반환한 메모리를 중복 반화할 수 없음

메모리 누수

  1. 동적 메모리르 받은 포인터 변수의 값을 delete를 사용하지 않고 변경한 경우
int other = 10;
int *p = new double[20];
p = other;	//p에 할당되었던 동적 메모리를 반환할수도 없고, 사용할 수도 없는 메모리가 되어버림
		//즉, 해당 메모리는 여전히 p와 연결되어있다고 생각하여, 다른 변수에게 해당 공간을 줄 수 없음
          	//=> 메모리 누수 발생
  1. 같은 포인터 변수에 반복적으로 new 를 사용하는 경우
char *alpha;
for(int i =0;i<10;i++)
	alpha = new char[10];	//계속 새로 할당받는 동적 메모리 공간을 가리키게 됨
    				//즉, 이전에 할당되었떤 동적 메모리 공간은 heap에 계속 남아있음
                    		//=> 메모리 누소 발생

프로그램이 종료되면, 운영체제는 누수 메모리를 모두 힙에 반환한다.

프로그램이 종료되기 전까지 누수 메모리는 사용하지 못하지만 공간은 차지하고 있기 때문에, 항상 동적 메모리를 할당할 때는 반드시 메모리 누수가 발생하지 않도록 주의할 필요가 있다.

0개의 댓글