CPP 초급 강좌 23. C++ 동적메모리 사용(new, delete, delete[]), nullptr
동적메모리
c++ 에서 동적 메모리를 사용하는 방법에 대해 봅시다.
먼저 c 에서의 메모리 할당 코드를 봅시다. 현제 c++에서도 사용할 수도 있는 코드입니다.
int* p = (int*)malloc(sizeof(int) * 10);
free(p);
malloc로 할당하고, free 로 해지합니다. 해당 코드는 int * 10의 메모리 영역을 할당합니다. 40byte
기본적으로 malloc 는 void* 형이 반환타입입니다. 원하는 형식으로 다시 캐스팅(int*) 을 하여 사용하여야 합니다.
c++ 프로젝트 에서도 아직 많이들 사용하고 있는 코드입니다. 필요할 경우도 분명 많구요.
그럼 이번에는 c++ 에서 지원되는 동적메모리 할당 코드를 봅시다.
int* p1 = new int; //a
delete p1;
int* p2 = new int[10]; //b
delete[] p2;
int* p3 = new int[5, 2];//c
delete[] p3;
a 는 데이터 타입을 하나 요구하고, 지우는 코드이다.
new 형식 delete 포인터 로 코딩하여 사용한다.
b 는 일차원 배열 데이터를 할당하는 방법이다.
특이한 것은 배열형식을 지울 때 delete[] 를 사용한다는 것이다.
c 는 이차원(다중 배열) 데이터 할당을 할때 사용하는 방법니다.
자중 배열이라고 해서 delete[][], delete[,] 이런 코드 사용하지 않고, delete[]를 사용한다.
c++ 에서는 new, delete 키워드로 동적 메모리를 사용할 수 있다.
new로 할당된 메모리는 c의 malloc와 달리 캐스팅할 필요가 없다.
사실 new로 호출시 malloc와 가장 큰 차이점은 생성사 호출에 있다. 아직 클래스의 이해도가 높지 않을거라 가정하여 해당 내용을 자세히 설명하지 않겟지만, 이런게 있구나 정도로 봐주시면 좋을 듯합니다.
nullptr
포인터 변수를 초기화 할 때 어떤 코드를 사용하시나요 다음 코드를 봅시다.
int* p1 = 0; //a
int* p2 = NULL; //b
int* p3 = nullptr; //c
a 는 0으로 초기화 하는 코드입니다. 많이들 사용하죠.
b 는 NULL 로 초기화 하는 코드입니다. a, b의 정확한 차이를 알고 있으신가요?
c 는 c++11 부터 추가된 새로운 키워드인 nullptr로 포인터 초기화를 한 코드입니다.
먼가 이유가 있으니 nullptr이 추가 된 거겠죠.
nullptr의 도입은 안전 과 코드 가독성 을 위함입니다.
설명하기 전에 리터럴 이란 용어를 알면 좋습니다.
리터럴은 데이터 그 자체를 의미합니다. 위의 코드에서는 0, NULL, nullptr 등이 리터럴이 될 수 있습니다.
0 이라는 녀석...
int i = 0;
double d = 0;
bool b = 0;
int* p = 0;
해당 초기화 코드들은 아무 문제없이 잘 빌드되고 동작하는 코드입니다. 그닥 뭐 특이점이 없습니다. 그럼 아래 코드를 추가로 봅시다.
void f(int value) { cout << "f(int value)" << endl; }
void f(double value) { cout << "f(double value)" << endl; }
void f(bool value) { cout << "f(bool value)" << endl; }
void f(int* value) { cout << "f(int* value)" << endl; }
int main()
{
int i = 0;
double d = 0;
bool b = 0;
int* p = 0;
f(0); //뭐가 호출될까요?
}
f(0)는 뭐가 호출될까요? 모두 0 값으로 초기화가 가능한데?
정답은 f(int) 입니다. 왜 일까요?
그것은 0 은 리터럴인데, 리터럴은 종류와 데이터 타입이 있습니다.
0과 같은 경우는 정수형 literal 에 int형 데이터 타입입니다. 그래서 f(int) 형으로 호출되는 것입니다.
그럼 다음 경우는 어떨까요?
//void f(int value) { cout << "f(int value)" << endl; }
void f(double value) { cout << "f(double value)" << endl; }
void f(bool value) { cout << "f(bool value)" << endl; }
void f(int* value) { cout << "f(int* value)" << endl; }
int main()
{
int i = 0;
double d = 0;
bool b = 0;
int* p = 0;
f(0); //뭐가 호출될까요?
}
매칭되던 타입을 주석처리 하여 삭제하였습니다. 어떻게 될까요?
이제부터는 f(0) 에서 영은 정확히 매칭되는 타입이 존재하지 않습니다.
f의 오버로드된 함수 모두가 가능성이 있어져서 호출이 모호하게 됩니다. 이런 경우는 빌드부터 되지 않습니다.
각 함수를 정확히 호출하기 위해 해당 리터럴로 호출하는 코드를 작성해봅시다.
f(0); //a
f(0.0); //b
f(false); //c
f((int*)0);//d
a 0
정수형 literal, int 데이터 타입
b 0.0
실수형 literal, double 데이터 타입
c false
boolean literal, bool 데이터 타입
d (int*)0
nullptrl 등장이전 포인터 리터럴 타입이 없었다. 위와 같은 방식으로 필요할 경우 호출하곤 했다.
존재하지 않았던 포인터 리터럴 타입의 등장
nullptr
f((int*)0);//d
f(nullptr);//d2
d, d2의 결과는 같다.
nullptr 은 pointer literal 이며, std::nullptr_t 데이터 타입이다.
가독성 문제?
아래 코드를 봅시다.
int* p = nullptr;
//...
//...
//...
if (p == 0)
{
//작업
}
해당 코드들이 한눈에 보일때는 p라는 것이 포인터라는 것을 알 수 있겠지만 많약 함수내부 코드이고 선언부와 조건문이 거리가 멀다(200라인 이상 차이가 있다.) 라 가정하자. 당신이 확인할 수 있는 코드는 if(p==0) 뿐이고 이후 내부에서 p를 뭔가 써야 한다면 p가 실제 어떤 변수인지, 아니면 포인터인지 알수 있는 방법이 있는가? 아마도 선언부를 보고 다시 와야 하겠지.
이럴땐 명시적으로 포인터라면 nullptr을 사용하면 어떨까?
if(p == nullptr)
한눈에 포인터로 사용하고 없는지 확인하는 코드일줄 알 수 있을 것이다.
다음 코드는 어떨까?
auto p = f();
if (p == 0)
{
//작업
}
p는 뭘까? 포인터라면 비교에 nullptr 표시해 주면 더 좋지 않을까?
std::nullptr_t
nullptr 이라는 리터럴은 std::nullptr_t 라는 데이터 형태를 가진다.
std::nulptr_t 는
모든 타입의 포인터로 암시적 변환이 된다.
정수 타입으로 변환 되지 않는다.
bool 값으로 직접 초기화는 가능하다.
코드로 정리하면 이러 하다.
int* p1 = nullptr; //ok 포인터 암시적 변환
void* p2 = nullptr; //ok 포인터 암시적 변환
void(*f) = nullptr; //ok 함수 포인터 암시적 변환
int n = nullptr; //error 정수로 변환 안됨
bool b1 = nullptr; //bool 형에 바로 대입 안됨
bool b2{ nullptr }; //ok 직접 초기화로는 사용 가능
봐주셔서 감사합니다.
'IT > C++(CPP)' 카테고리의 다른 글
CPP 초급 강좌 25. 객체지향 프로그램밍 클래스, 생성자, 소멸자, 파일분할, 클래스 탬플릿 (4) | 2020.11.04 |
---|---|
CPP 초급 강좌 24. 객체지향 프로그램밍 객체화, 접근 지정자 맛보기 (9) | 2020.11.03 |
CPP 초급 강좌 22. C++ Explict type conversion(casting) (6) | 2020.11.01 |
CPP 초급 강좌 21. C++ reference return, rvalue reference && (0) | 2020.10.31 |
CPP 초급 강좌 20. C++ const reference (16) | 2020.10.30 |
댓글