삶의 공유

[C++] Null Ptr (널포인터) 기본 다지기 본문

Programing/C++

[C++] Null Ptr (널포인터) 기본 다지기

dkrehd 2024. 2. 17. 09:33
728x90
반응형

 

오늘은 C++11 에서 부터 추가된 Null Ptr(널 포인터) 에 대해 살펴 보겠다.

 

 

nullptr

 

 

먼저 아래의 코드를 보자

 

위의 2줄은 ok이지만, 마지막 2줄에서는 error가 발생 한다.  왜 일까?

 

#include <iostream>

int main(int argc, const char * argv[]) {
    // insert code here...
    int n1 = 0;   // 1. ok
    int* p1 = 0;  // 2. ok
    
    int* p2 = 10; // 3. error
    int* p3 = n1; // 4. error
 
}

 

 

먼저 int n1 = 0; 구문에서 이 '0'에 대해서 생각 해보자.

 

이 '0'은 바로 정수(int)형 literal 이다. 즉 포인터 변수 초기화에 사용이 될 수 있다. (포인터로 암시적 형 변환) 그렇기 때문에 2번 구문에서 error 가 발생 하지 않는 것이다.

 

하지만 3번 구문에서는 error가 발생한다. 이 의미는 바로 0이 아닌 다른 정수형 literal은 포인터로 암시적 변환이 될 수 없다는 뜻이다. 그래서 3번 구문에서 error가 발생한 것이다.

 

4번 구문은 변수의 값이 0이지만 error가 발생했다. 이것은 또 왜일까? 바로 변수에 담겨있기 때문이다. 즉, 변수도 포인터로 암시적 변환이 될수 없다는 의미이다.

 

그럼 이 포인터를 초기화 하기 위해서는 0 외에는 쓸수가 없을까? 

이 포인터를 초기화 하기 위해 C++11 부터 null pointer 를 도입하게 되었다. 널 포인터는 모든 종류(타입)의 포인터 변수를 초기화 하는데 사용가능하다.

그렇기 때문에 위의 3,4번의 문장을 nullptr로 대체하면 error가 없다. 또한 함수형 포인터도 nullptr keyword를 이용하여 초기화 할 수 있다. 하지만 정수(실수)형 변수 초기화 에는 사용 될 수 없다.

 

 

#include <iostream>

int main(int argc, const char * argv[]) {
    // insert code here...
    int n1 = 0;
    int* p1 = 0;
    
    // int* p2 = 10; // error
    // int* p3 = n1; //error
    
    int* p4 = nullptr;     // ok
    void(*p5)() = nullptr; // ok
     
    // int n2 = nullptr;  // error
}

 

 

하나의 예시를 더 들어보겠다.

 

#include <iostream>

void foo(int n) { std::cout << "int" << std::endl;}
void foo(void* p) { std::cout << "void*" << std::endl;}

int main(int argc, const char * argv[]) {
    // insert code here...
    
    foo(0);  // int
    foo((void*)0); // *void
    foo(nullptr);  // *void
    
}

 

만약 위의 코드에서 foo() 함수를 호출 하면 어느 함수가 호출이 될까?

바로 "int"라는 문자가 출력이 된다. 만약 foo(int n) 함수가 없었다면 "void*" 가 출력이 될 것이다. 본래의 형식인 int형이 먼저 호출이 되는 것이다. 

 

그럼 "void*"를 호출이 하고 싶으면 어떻게 해야 될까? 그렇다. 앞에서 살펴본것 처럼 nullptr을 인자로 넣어주면 된다. 

 

 

nullptr_t

 

 

 

예를 하나 더 들어보겠다.

 

#include <iostream>

void foo(int n) { std::cout << "int" << std::endl;}

void foo(double n) { std::cout << "double" << std::endl;}
void foo(bool n) { std::cout << "bool" << std::endl;}
void foo(char* n) { std::cout << "char*" << std::endl;}



int main(int argc, const char * argv[]) {
    // insert code here...
    
    foo(0);     // foo (int) 0 의 기본 형으로 들어감
    foo(0);
    foo(0);
    foo(0);
}

 

 

위의 코드에서 foo(0)를 호출 하면 어떻게 될까?

 

0이 일반적으로 int, double bool, char 모두 암시적 형변환이 가능한 표현이지만, 위의 foo(0)함수는 0의 기본 형인 int만 호출이 되기 때문에 실행 하면 int만 출력이 된다.

 

이를 각 타입에 맞게 변환을 해보자

 

 

#include <iostream>

void foo(int n) { std::cout << "int" << std::endl;}

void foo(double n) { std::cout << "double" << std::endl;}
void foo(bool n) { std::cout << "bool" << std::endl;}
void foo(char* n) { std::cout << "char*" << std::endl;}



int main(int argc, const char * argv[]) {
    // insert code here...
    
    foo(0);      // foo (int) 0 의 기본 형으로 들어감
    foo(0.0);    // foo (double)
    foo(false);  // foo(bool)
    foo(nullptr); // foo(char*)
}

 

 

여기서 눈여겨 봐야할 핵심은 모든 리터럴은 타입이 있다는 것이다

 

0 0.0 false nullptr
정수형 리터럴 실수형 리터럴 bool형 리터럴(키워드) 포인터형 리털러(키워드)
int double bool std::nullptr_t

 

 

std::nullptr_t 타입은 이 무엇일까?

바로 nullptr의 타입으로서 모든 종류(타입)의 포인터로 암시적 형변환이 가능하다.

 

활용 방법에 대해 바로 예제를 통해 확인을 해보자.

 

int main(int argc, const char * argv[]) {
    // insert code here...
    
    std::nullptr_t null = nullptr;
    int* p1  = null;
    char* p2 = null;
}

 

 

바로 위와 같이  활용 할 수있다.

 

 

nullptr과 변환

 

다음의 예제를 보자

 

int main(int argc, const char * argv[]) {
    // insert code here...
    
    int* p1 = nullptr;     // ok
    double* p2 = nullptr;  // ok
     
    int n1 = nullptr;      // error
    int n2{nullptr};       //error
    
    bool b1 = nullptr;     // error
    bool b2{nullptr};      // ok
    
    if (nullptr) {}        // ok
}

 

 

포인터 변수를 nullptr로 초기화는 어떤 타입의 포인터 변수든 문제 없이 가능하지만 int형과 같은 변수를 초기화할때는 복사초기화(=), 직접초기화({ }) 모두 error를 발생 시키게 된다.

 

하지만 예외로 bool형의 경우 직접 초기화를 하게 되면 error가 발생하지 않는다. 이게 되는 이유가 바로 아래 구문의 if구문에 들어가기 위해서는 bool의 직접 초기화를 지원 해야되기 때문에 직접 초기화 구문을 허용하게 되는 것이다.

 

 

지금 까지 공부한 내용을 한문장으로 요약 하면 다음과 같다

 

포인터 변수를 초기화 할때는 "0"을 사용하지 말고 "nullptr"을 사용하자는 것이다!

 

 

 

반응형