동적 메모리 할당 및 반환

shinyeongwoon·2022년 11월 22일
0

C++

목록 보기
2/10

정적 할당

  • 변수 선언을 통해 필요한 메모리 할당
    많은 양의 메모리는 배열 선언을 통해 할당

동적 할당

  • 필요한 양이 예측되지 않는 경우, 프로그램 작성 시 할당 받을 수 없음
  • 실행 중에 힙 메모리 에서 할당

📌 힙(heap)으로 부터 할당
힙은 운영체제가 프로세스의 실행을 시작 할때 동적 할당 공간으로 준 메모리 공간

  • C 언어의 동적 메모리 할당 : malloc()/free() 라이브러리 함수 사용
  • C++의 동적 메모리 할당/반환

👉 new 연산자
기본 타입 메모리 할당, 배열 할당, 객체 할당, 객체 배열 할당
객체의 동적 생성 - 힙 메로리로 부터 객체를 이한 메모리 할당 요청
객체 할당 시 생성자 호출

👉 delete 연산자
new로 할당 받은 메모리 반환
객체의 동적 소멸 - 소멸자 호출 뒤 객체를 힙에 반환

🍙 참고 :

  • 코드(code) 영역
    메모리의 코드(code) 영역은 실행할 프로그램의 코드가 저장되는 영역으로 텍스트(code) 영역이라고도 부릅니다.

CPU는 코드 영역에 저장된 명령어를 하나씩 가져가서 처리하게 됩니다.

  • 데이터(data) 영역
    메모리의 데이터(data) 영역은 프로그램의 전역 변수와 정적(static) 변수가 저장되는 영역입니다.

데이터 영역은 프로그램의 시작과 함께 할당되며, 프로그램이 종료되면 소멸합니다.

  • 스택(stack) 영역
    메모리의 스택(stack) 영역은 함수의 호출과 관계되는 지역 변수와 매개변수가 저장되는 영역입니다.

스택 영역은 함수의 호출과 함께 할당되며, 함수의 호출이 완료되면 소멸합니다.

이렇게 스택 영역에 저장되는 함수의 호출 정보를 스택 프레임(stack frame)이라고 합니다.

스택 영역은 푸시(push) 동작으로 데이터를 저장하고, 팝(pop) 동작으로 데이터를 인출합니다.

이러한 스택은 후입선출(LIFO, Last-In First-Out) 방식에 따라 동작하므로, 가장 늦게 저장된 데이터가 가장 먼저 인출됩니다.

스택 영역은 메모리의 높은 주소에서 낮은 주소의 방향으로 할당됩니다.

  • 힙(heap) 영역
    메모리의 힙(heap) 영역은 사용자가 직접 관리할 수 있는 ‘그리고 해야만 하는’ 메모리 영역입니다.

힙 영역은 사용자에 의해 메모리 공간이 동적으로 할당되고 해제됩니다.

힙 영역은 메모리의 낮은 주소에서 높은 주소의 방향으로 할당됩니다.

출처 및 참고 : https://junghyun100.github.io/%ED%9E%99-%EC%8A%A4%ED%83%9D%EC%B0%A8%EC%9D%B4%EC%A0%90/

new 와 delete 연산자

  • 형식
    데이터타입 *포인터변수 = new 데이터타입;
    delete 포인터 변수;

#include <iostream>

using namespace std;

int main(){
  //포인터 변수 선언
  int *p;
  // 포인터 변수에 new int 대입
  // 포인터 변수에는 주소만 담을 수 있으므로 new를 통한 객체 생성 시 주소를 반환해준다는 의미
  p = new int;
  //메모리 할당을 실패할 경우 null을 반환, null => false / !로 반전을 주어 null 일 경우 메세지 출력 및 종료
  if(!p){
    cout << "메모리 할당 불가!!";
    return 0;
  }
  
  //p : 동적 할당 받은 주소 , *p : 동적 할당 받은 주소의 값
  *p = 5;
  // n에 포인터변수의 값을 담음
  int n = *p;

  cout << *p << endl;
  cout << n << endl;
  //할당 받은 메모리 반환
  delete p;

}

결과
5
5

delete 사용 시 주의 사항

  • 적절치 못한 포인터로 delete하면 실행 시간 오류 발생
    동적으로 할당 받지 않은 메모리 반환 - 오류
int n;
int *p = &n;
delete p;//실행 시간 오류
//포인터 p가 가리키는 메모리는 정적할당 받은 메모리 공간

//-------------별개
int *p = new int;
delete p;
delete p; 실행 시간 오류. 이미 반환한 메모리를 중복 반환 할 수 없음

배열의 동적 할당 및 반환

  • 형식
    데이터타입 *포인터변수 = new 데이터타입[배열의 크기];
    delete [] 포인터변수;

#include <iostream>

using namespace std;

int main(){

  int n;
  // n 변수에 배열 길이 입력 받기
  cin >> n;
  // n이  0보다 작으면 프로그램 종료
  if(n <= 0) return;
  //포인터 변수에 int형 n개의 배열 생성
  int *p = new int[n];
  //메모리 할당에 실패 시 프로그램 종료
  if(!p){
    return 0;
  }
  //배열 순차 Scan을 통해 각 요소 입력 받기
  for(int i=0;i<n;i++)
    cin >> p[i];
  
  int sum = 0;
  
  //각 요소를 순차 Scan하며 누산하기
  for(int i=0;i<n;i++)
    sum += p[i];
  //평균 출력
  cout << "평균" << sum/n << endl;
  //배열 반환
  delete p[]

}

동적 할당 메모리 초기화 및 delete 시 유의 사항

  • 동적 할당 시 초기화
    데이터타입 *포인터변수 = new 데이터타입(초기화);
//20으로 초기화된 int 타입 할당
int *pInt = new int(20);
//a로 초기화 된 char 타입 할당
char *pChar = new char('a');
  • 베열은 동적 할당 시 초기화 불가능
int *pArry = new int[10](20); //구문 오류. 컴파일 오류 발생
int *pArry = new int(20)[10]; //구문 오류. 컴파일 오류 발생
  • delete 시 [] 생략
    컴파일 오류는 발생하지 않지만 비정상적인 반환
int *p = new int[10];
delete p; // delete p[]

int *q = new int; 
delete [] q; // delete q;

객체의 동적 생성 및 반환

클래스이름 *포인터변수 = new 클래스이름;
클래스이름 *포인터변수 = new 클래스이름(생성자 매개변수 리스트);
delete 포인터변수;

#include <iostream>

using namespace std;

class Circle{
  int radius;
public:
  Circle();
  Circle(int r);
  ~Circle();
  void setRadius(int r){radius = r;}
  double getArea(){return 3.14*radius*radius} 
};

Circle::Circle(){
  radius = 1;
  cout << "생성자 실행 radius = " << radius << endl;
}

Circle::Circle(int r){
  radius = r;
  cout << "생성자 실행 radius = " << radius << endl;
}

Circle::~Circle(){
  cout << "소멸자 실행 radius = " << radius << endl;
}

int main(){
  // 객체 포인터 변수 생성
  Circle *p,*q;
  // 객체 생성 : 기본 생성자 사용
  p = new Circle;
  // 객체 생성 : 인자 1를 가진 생성자 사용
  q = new Circle(30);
  // 포인터 변수를 사용한 멤버 접근으로  -> 사용
  cout << p ->getArea() << endl << q -> getArea() <<endl;
  //생성 순서와 관계 없이 원하는 순서대로 delete 할 수 있음
  delete p;
  delete q;
  
}

실행 결과
생성자 실행 radius = 1
생성자 실행 radius = 30
3.14
2826
소멸자 실행 radius = 1
소멸자 실행 radius = 30

객체 배열의 동적 생성 및 반환

클래스이름 *포인터변수 = new 클래스이름 [배열크기];
delete [] 포인터변수;

  • 동적으로 생성된 배열도 보통 배열처럼 사용
//3개의 Circle 객체 배열의 동적 생성
Circle *pArry = new Circle[3];
//Radius Setting
pArry[0].setRadius(10);
pArry[1].setRadius(20);
pArry[2].setRadius(30);
//각각 객체의 getArea() 함수 호출
for(int i = 0; i < 3 ; i++){
  cout << pArry[i].getArea();
}
  • 포인터로 배열 접근
pArry -> setRadius(10);
(pArry+1) -> setRadius(20);
(pArry+2) -> setRadius(30);

for(int i = 0; i < 3 ; i++){
  cout << (pArry+i)->getArea();
}
  • 배열 소멸
//배열 소멸 시 각 원소의 객체 소멸자 별도 실행, 생성의 반대순
delete [] pArray;

동적 메모리 할당과 메모리 누수

this 포인터

  • 객체 자신 포인터
  • 클래스의 멤버 함수 내에서만 사용
  • 개발자가 선언하는 변수가 아님, 컴파일러가 선언하는 변수
    멤버 함수에 컴파일러에 의해 묵시적으로 삽입 선언되는 매개 변수
class Circle{
   int radius;
public:
  // this는 객체 자신의 포인터!! 이므로 -> 를 통하여 멤버에 접근
  Circle(){ this -> radius = 1;}
  Circle(int radius) { this-> radius = radius; }
  void setRadius(int radius) { this -> radius = radius; }
}

멤버 함수가 객체 자신의 주소를 리턴할 때

연산자 중복 시 매우 필요

class Sample{
public:
  Sample* f(){
  	...
    return this;
  }
}

this의 제약 사항

  • 멤버 함수가 아닌 함수에서 this 사용 불가
    객체와의 관련성이 없기 때문
  • static 멤버 함수에서 this 사용 불가
    객체가 생성되지 전에 static 함수 호출 불가

0개의 댓글