C++

<C++> nullptr

hanseongbugi 2024. 4. 3. 18:33

nullptr(널포인터)?

  • nullptr은 C++11부터 추가된 새로운 키워드이다.
    • 기존 C, C++은 NULL이나 0을 사용해 포인터를 초기화 하였다.
  • nullptr은 널 포인터 값(Null Pointer Value)를 나타내는 포인터 리터럴(Pointer Literal)이다.
    • 즉, 포인터를 표현하는 값 중 널을 표현한 값이다.
int* ptr1 = 0;
int* ptr2 = nullptr;
  • 기존의 방식과 동일하게 0으로 초기화할 수 있지만 nullptr을 사용하는 것이 직관적
  • 이전에 0을 사용해서 초기화하는 방식은 실제 0을 저장시 모호한 상황이 발생할 수 있음
  • nullptr 사용 시 모호한 상황이 아닌 포인터만 체크할 수 있다는 장점이 있음

nullptr_t 타입

  • C++11부터 만들어진 nullptr 타입은 <cstddef> 헤더에 있는 std::nullptr_t 타입이다.
  • 0이 int 타입, 0.0이 double 타입, 0.0f가 float 타입인듯 모든 변수에는 데이터 타입이 존재
    • nullptr은 std::nullptr_t라는 타입
  • std::nullptr_t 타입은 모든 타입의 포인터로 암시적 형 변환이 가능
int* ptr1 = nullptr;                    // int 포인터
char* ptr2 = nullptr;                   // char 포인터
double* ptr3 = nullptr;                 // double 포인터
void (*func1) (int a, int b) = nullptr; // 함수 포인터1
void (*func2) () = nullptr;             // 함수 포인터2
  • 위와 같이 모든 타입의 포인터를 초기화할 수 있다.

장점

  • nullptr 사용 시 가독성이 좋아지는 장점이 있다.
  • C++에서 nullptr 사용 시 NULL을 체크하는 것이 뚜렸해진다.
int main(void) {
    int* a = 0;
 
    // a가 int 값의 0인지 포인터 타입인지 모호함
    if (a == 0)
    {
        cout << "a == 0" << endl;
    }
 
    // a가 포인터 타입인지 명확해짐
    if (a == nullptr)
    {
        cout << "a == nullptr" << endl;
    }
}
  • 첫번째 if문에서 0으로 널체크 시 값이 0인지 포인터 타입인지 알기 어렵다
  • 2번째 if문에서 nullptr을 사용 시 a 변수가 포인터인지 명확하게 알 수 있다.
  • 따라서 코드의 안정성이 올라가고 가독성이 높아진다.

NULL과 nullptr의 차이점

  • 컴파일러의 종류에 따라 다르지만 NULL은 아래와 같이 정의되어 있다.
// stddef.h
#if defined(__need_NULL)
#undef NULL
#ifdef __cplusplus
#  if !defined(__MINGW32__) && !defined(_MSC_VER)
#    define NULL __null
#  else
#    define NULL 0
#  endif
#else
#  define NULL ((void*)0)
#endif
  • 이것은 쉽게 설명하면 C++에서는 NULL을 0으로 사용하고 C에서는 NULL을 ((void *) 0)으로 치환해서 사용한다는 것
  • NULL은 진짜 NULL을 가리키는 포인터가 아닌 숫자 0인 것이다.
  • 그러니 가짜가 아닌 진짜 널을 가리키는 nullptr을 사용해야하는 것이다.
  • NULL과 nullptr의 차이점은 아래와 같다.
#include<iostream>
using namespace std;

void func(int a){ cout<<"int 호출"<<'\n'; }
void func(int* b){ cout<<"int* 호출"<<'\n'; }

int main(){
    cout<<"\n NULL, nullptr 비교1\n";
    func(0);
    // func(NULL); #define NULL __null이므로 error
    func((int*)0);
    func(nullptr);

    cout<<"\n NULL, nullptr 비교2\n";
    int* ptr1 = NULL;
    int* ptr2 = nullptr;
    if(ptr1 == NULL){ cout<<"2.1 NULL == NULL\n"; }
    if(ptr2 == NULL){ cout<<"2.2 nullptr == NULL\n"; }
    if(ptr1 == nullptr){ cout<<"2.3 NULL == nullptr\n"; }
    if(ptr2 == nullptr){ cout<<"2.4 nullptr == nullptr\n"; }
    if(ptr1 == ptr2){ cout<<"2.5 NULL == nullptr\n"; }

    cout<<"\n NULL, nullptr 비교3\n";
    int a = 0;
    if(a==NULL) cout<<"3.1 int 타입 0 == NULL\n";
    // if(a==nullptr) cout<<"3.2 int 타입 0 == nullptr\n";
}

// 출력

 NULL, nullptr 비교1
int 호출
int* 호출
int* 호출

 NULL, nullptr 비교2
2.1 NULL == NULL
2.2 nullptr == NULL
2.3 NULL == nullptr
2.4 nullptr == nullptr
2.5 NULL == nullptr

 NULL, nullptr 비교3
3.1 int 타입 0 == NULL
  • 예제 1
    • 인자로 0과 NULL을 넣게 된다면 컴파일러의 종류에 따라 func(int a) 함수가 호출될 수 있다.
    • 표준 C++11에 의하면 NULL은 #define NULL __null이며 __null은 long 타입이므로 위 함수로 호출될 수 없다.
    • 중요한 점은 NULL은 정수 형태의 값이며 nullptr은 포인터 값을 뜻한다는 것이다.
  • 예제 2
    • 2번째 예제에서 NULL과 nullptr은 완전히 다르다고 생각할 수 없다.
    • NULL이 0이긴 하지만 nullptr과 비교 시 true가 나오는 것을 알 수 있다.
    • 예제 2번의 0은 int의 0이 아닌 int*의 0이므로 널을 가리키는 것이 맞기 때문에 nullptr과 비교 가능한 것이다.
  • 예제 3
    • int 타입의 0과 NULL, nullptr 비교 시 차이를 명확하게 알 수 있다.
    • int 타입의 0은 NULL과 비교가 가능하다.
    • nullptr과 비교 시 비교 자체가 불가능하다.

 

 

출처

https://blockdmask.tistory.com/501

 

[C++] nullptr (널 포인터 리터럴)

안녕하세요. BlockDMask입니다. 오늘은 C++11에서 도입된 새로운 키워드 nullptr에 대해서 이야기해보려 합니다. 기존 C언어, C++을 사용하시는 많은 분들이 0, NULL 이렇게 널을 사용하셨을 텐데요. C++11

blockdmask.tistory.com