[C++] nullptr 리터럴( literal )과 NULL의 차이점

리터럴( literal )이 무엇인가?

리터럴( literal )은 값을 직접 나타내는 프로그램 요소입니다.

 

아래의 예제들을 통해 리터럴의 개념을 알 수 있습니다.

#include <math.h>

int main(){

    int variable = 10;          // integer literal
    const int answer = 42;      // integer literal
    double d = sin(108.87);     // floating point literal passed to sin function
    bool b = true;              // boolean literal
    char c = 'A';               // char literal
    const char* pstr = "Hello !!";  // string literal
}

첫 번째 줄의 10은 int 타입 리터럴이고, variable은 int 타입 변수입니다.

 

두 번째 줄의 42는 int 타입 리터럴이고, answer는 상수 속성의 int 타입 변수입니다.

 

마지막 줄의 "Hello!!"는 char [] 타입의 리터럴이고, pstr는 상수 속성의 char 타입 포인터 변수입니다.

 

그리고, NULL은 int 타입 리터럴 0을 대신한 기호일 뿐입니다.

#define NULL	0

int variable = NULL;            // integer literal

 

nullptr 리터럴( literal )

nullptr은 C++ 11에 도입한 리터럴로, 포인터가 어떠한 객체도 가리키고 있지 않다는 것을 나타내는 값입니다.

이것은 기존에 같은 의미로 쓰였던 NULL을 대체하기 위해 만들어졌습니다.

 

NULL을 대체하기 위해 만들어진 만큼, 지시하는 대상이 없는 포인터를 나타내는 의미로서의 NULL이 사용되었던 위치엔 전부 사용할 수 있습니다.

int obj_pointer = nullptr;	// int 타입 포인터 초기화

 

이렇게, NULL의 사용을 대체하려고 하는 이유는, NULL이 어떤 값으로 정의되어 있는지에 따라( 대부분은 0으로 정의 ), 같은 코드를 사용한다고 하더라도 다른 결과가 나올 수 있기 때문입니다.

#ifdef NULL
#undef NULL
#define NULL 0  // 컴파일러마다 정의가 다르므로 설명하기 위해 재정의
#endif

bool isTrue(){
    // Do something...
    return false;
}

void compute(int val){
    cout << "compute by val\n";
}

void compute(int* ptr){
    cout << "compute by pointer\n";
}

int main(){

    int arr[] = { 1, 2, 3, 4, 5 };
    int* ptr = arr;
    
    if ( isTrue()){
        compute(ptr);
    }
    else{
        compute(NULL);  // 포인터 사용을 의도했지만, 다른 함수를 호출
    }
}

▼출력

compute by val

아마, 위에 코드의 개발자는 포인터를 사용한 함수가 호출되길 의도했을 것입니다.

하지만, 출력된 결과와 같이 NULL은 값이 0인 int로 사용되었습니다.

 

그리고, 만약 위에서 전처리기를 사용해서 NULL을 재정의 하지 않았다면, 컴파일러에 따라, 함수로 보내지는 인자의 모호성으로 오류가 발생할 수도 있습니다.

 

그러면, nullptr은 어떻게 이 문제를 해결하는 걸까요?

 

nullptr의 타입은 무엇인가?

nullptr의 std::nullptr_t 타입의 리터럴( literal )입니다.

그리고, nullptr_t는 지시하고 있는 객체가 없는 포인터를 정의하기 위한 만들어진 새로운 타입입니다.

 

이 타입을 만든 이유는, 모든 타입의 포인터를 초기화시켜야 할 리터럴이 기존의 특정 포인터 타입이 될 수 없기 때문입니다.

그리고, 이 타입은 암시적이든 명시적이든 상관없이 모든 포인터 타입으로 변환이 가능하지만, 다른 타입이 nullptr_t 타입으로 변환될 수는 없도록 구현되었습니다.

 

이제, 이러한 정보를 가지고, 다시 코드를 보면 문제를 어떻게 해결하고 있는지 알 수 있을 것입니다.

void compute(int val){
    cout << "compute by val\n";
}

void compute(int* ptr){
    cout << "compute by pointer\n";
}

int main(){

    int arr[] = { 1, 2, 3, 4, 5 };
    int* ptr = arr;
    
    if ( isTrue()){	// 중요하지 않으므로 생략
        compute(ptr);
    }
    else{
        compute(nullptr);  // nullptr로 대체
    }
}

▼출력

compute by pointer

먼저, 함수 compute를 호출하면, 인자인 nullptr은 int 포인터 타입으로 암시적인 변환이 일어납니다.

nullptr을 int 타입으로 변경하는 변환은 정의되어 있지 않기 때문입니다.

그다음에, 변환된 값 (int*) 0이 포인터를 매개 변수로 받는 함수에 넘겨집니다.

 

이로써, NULL 사용 시 발생 가능한 모호성은 완전히 사라졌습니다.

 

참고로, 다음의 함수는 nullptr만이 호출할 수 있습니다.

다른 타입의 값으로 시도하면, 컴파일러가 오류를 발생할 것입니다.

void compute_nullptr( nullptr_t ptr){
    // Do something...
}

int main(){
    compute_nullptr(nullptr);
}

 

 

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유