nullptr은 정확히 무엇입니까?


570

이제 많은 새로운 기능을 갖춘 C ++ 11이 있습니다. 흥미롭고 혼란스러운 것은 (적어도 나를 위해) new nullptr입니다.

음, 더 이상 불쾌한 매크로가 필요하지 않습니다 NULL.

int* x = nullptr;
myclass* obj = nullptr;

아직도, 나는 어떻게 nullptr작동 하지 않습니다 . 예를 들어, Wikipedia article 은 다음과 같이 말합니다.

C ++ 11 은 고유 한 널 포인터 상수 (NULLptr)로 사용할 새 키워드 를 도입하여이를 정정합니다 . 그것은이다 형 nullptr_t 어떠한 포인터 또는 포인터 타입 투 부재 유형 암시 컨버터블 필적. bool을 제외하고는 암시 적으로 변환하거나 정수 유형과 비교할 수 없습니다.

키워드와 유형의 인스턴스는 어떻습니까?

또한 nullptr좋은 예보다 우월한 또 다른 예가 0있습니까 ( 위키 백과 하나 외에) ?


23
관련 사실 : nullptrC ++ / CLI의 관리 핸들에 대한 널 참조를 나타내는 데에도 사용됩니다.
Mehrdad Afshari

3
Visual C ++를 사용할 때 네이티브 C / C ++ 코드와 함께 nullptr을 사용하고 / clr 컴파일러 옵션으로 컴파일하면 컴파일러에서 nullptr이 네이티브 또는 관리되는 널 포인터 값을 나타내는 지 여부를 판별 할 수 없습니다. 컴파일러가 의도를 명확하게하려면 nullptr을 사용하여 관리되는 값을 지정하거나 __nullptr을 사용하여 기본 값을 지정하십시오. Microsoft는이를 구성 요소 확장으로 구현했습니다.
cseder

6
되어 nullptr_t, 하나 명의 멤버가 보장 nullptr? 따라서 함수가 반환 nullptr_t되면 컴파일러는 함수의 본문에 관계없이 반환되는 값을 이미 알고 있습니까?
Aaron McDaid

8
@AaronMcDaid std::nullptr_t는 인스턴스화 할 수 있지만 nullptr유형이로 정의되어 있으므로 모든 인스턴스가 동일합니다 typedef decltype(nullptr) nullptr_t. 유형이 존재하는 주된 이유는 nullptr필요한 경우 함수가 특히 오버로드되도록 잡을 수 있기 때문입니다. 예를 보려면 여기 를 참조 하십시오 .
저스틴 타임-복원 모니카

5
0은 null 포인터가 아니며 null 포인터는 0 리터럴을 포인터 유형 으로 캐스팅 하여 얻을 수있는 포인터이며 정의 에 따라 기존 객체를 가리 키지 않습니다 .
스위프트-금요일 파이

답변:


403

키워드와 유형의 인스턴스는 어떻습니까?

이것은 놀라운 일이 아닙니다. 둘 다 truefalse(키워드입니다 및 리터럴로 그들은 유형이 bool). nullptr은 유형 의 포인터 리터럴 이며 std::nullptr_tprvalue입니다 (를 사용하여 주소를 사용할 수 없음 &).

  • 4.10포인터 변환에 대해서는 유형의 prvalue std::nullptr_t가 널 포인터 상수이며 적분 널 포인터 상수가로 변환 될 수 있다고합니다 std::nullptr_t. 반대 방향은 허용되지 않습니다. 이를 통해 포인터와 정수 모두에 대한 함수를 오버로드 nullptr하고 포인터 버전을 선택하도록 전달할 수 있습니다. 통과 NULL하거나 0혼란스럽게 int버전을 선택합니다 .

  • nullptr_t정수형에 대한 캐스트는 을 필요로하며 정수형에 reinterpret_cast대한 캐스트와 동일한 의미를 갖습니다 (void*)0(매핑 구현이 정의 됨). A reinterpret_castnullptr_t포인터 유형으로 변환 할 수 없습니다 . 가능한 경우 암시 적 변환에 의존하거나 사용하십시오 static_cast.

  • 표준은 sizeof(nullptr_t)이어야 sizeof(void*)합니다.


오, 살펴본 후 조건부 연산자가와 같은 경우 0을 nullptr로 변환 할 수없는 것 같습니다 cond ? nullptr : 0;. 내 답변에서 제거되었습니다.
Johannes Schaub-litb

88
참고 NULL도 보장되지 않습니다 0. 이 0L경우 호출 void f(int); void f(char *);이 모호 할 수 있습니다. nullptr항상 포인터 버전을 선호하며 절대로 호출하지 않습니다 int. 또한 nullptr 변환 할 수 있습니다 bool(초안은에서 말합니다 4.12).
Johannes Schaub-litb

@litb : f (int)와 f (void *)와 관련하여 f (0)은 여전히 ​​모호합니까?
Steve Folly

27
@Steve, 아니 int버전 을 호출합니다 . 그러나 f(0L)때문에, 모호 long -> intaswell 등 long -> void*모두 동등하게 비용이 많이 든다. 따라서 0L컴파일러에 NULL이 있으면 f(NULL)두 함수가 주어지면 호출 이 모호합니다. nullptr물론 그렇지 않습니다 .
Johannes Schaub-litb

2
@SvenS (void*)0C ++에서 와 같이 정의되어서는 안됩니다 . 그러나 임의의 null 포인터 상수로 정의 할 수 있으며 값이 0 인 정수는 nullptr충족됩니다. 그래서, 가장 확실히하지 않습니다 하지만 . (당신은 btw. 나를 핑하는 것을 잊었다.)
중복 제거기

60

에서 nullptr : A 타입 - 안전하고 분명한 널 포인터 :

새로운 C ++ 09 nullptr 키워드는 버그가 많고 약한 형식의 리터럴 0과 악명 높은 NULL 매크로를 대체하여 범용 null 포인터 리터럴 역할을하는 rvalue 상수를 지정합니다. 따라서 nullptr은 30 년이 넘는 창피, 모호함 및 버그를 종식시킵니다. 다음 섹션은 nullptr 기능을 제공하고 NULL 및 0의 질병을 치료하는 방법을 보여줍니다.

다른 참고 문헌 :


17
C ++ 09? 2011 년 8 월 이전에는 C ++ 0x로 표시되지 않았습니까?
Michael Dorst

2
@anthropomorphic 음 그 목적입니다. C ++ 0x는 2008 년 또는 2009 년에 완료 될지 알 수 없었기 때문에 여전히 진행중인 작업에서 사용되었습니다. 실제로 C ++ 0B는 C ++ 11을 의미합니다. stroustrup.com/C++11FAQ.html
mxmlnkn

44

왜 C ++ 11에서 nullptr을 사용해야합니까? 무엇입니까? 왜 NULL이 충분하지 않습니까?

C ++ 전문가 인 Alex Allain은 여기에 완벽하게 말합니다 (굵게 강조 표시).

... 다음 두 함수 선언이 있다고 상상해보십시오.

void func(int n); 
void func(char *s);

func( NULL ); // guess which function gets called?

두 번째 함수가 호출되는 것처럼 보이지만 결국 포인터로 보이는 것을 전달하는 것은 실제로 호출되는 첫 번째 함수입니다! 문제는 NULL이 0이고 0이 정수이므로 func의 첫 번째 버전이 대신 호출된다는 것입니다. 이것은 항상 일어나지 않지만 이런 일이 발생하면 매우 실망스럽고 혼란스러운 일입니다. 무슨 일이 일어나고 있는지에 대한 세부 사항을 모른다면 컴파일러 버그처럼 보일 수 있습니다. 컴파일러 버그처럼 보이는 언어 기능은 원하는 것이 아닙니다.

nullptr을 입력하십시오. C ++ 11에서 nullptr은 NULL 포인터를 나타내는 데 사용될 수있는 새로운 키워드입니다. 다시 말해, 이전에 NULL을 작성했던 곳에서는 nullptr을 대신 사용해야합니다. 프로그래머에게는 더 이상 명확하지 않지만 (모든 사람이 NULL의 의미를 알고 있음) 컴파일러에 더 명확합니다. 더 이상 포인터로 사용될 때 0이 특별한 의미를 갖는 데 사용되지 않습니다.

Allain은 다음과 같이 기사를 끝냅니다.

이 모든 것에 관계없이 C ++ 11의 경험 법칙은 과거에 nullptr다르게 사용했을 때마다 사용하기 시작하는 것 NULL입니다.

(내 말) :

마지막으로, 그것이 nullptr객체 라는 것을 잊지 마십시오 . 그것은 어디서나 사용할 수 있습니다 NULL이전에 사용했지만 어떤 이유로 유형을 필요로하는 경우, 그것의 유형을 추출 할 수있다 decltype(nullptr), 또는 직접 설명 std::nullptr_t단순히 인 typedefdecltype(nullptr).

참고 문헌 :

  1. Cprogramming.com : C ++ 11의 더 나은 유형-nullptr, enum 클래스 (강력한 유형의 열거) 및 cstdint
  2. https://en.cppreference.com/w/cpp/language/decltype
  3. https://en.cppreference.com/w/cpp/types/nullptr_t

2
나는 당신의 대답이 과소 평가되었다고 말해야합니다. 귀하의 예를 통해 이해하기가 매우 쉽습니다.
mss

37

둘 이상의 유형에 대한 포인터를 수신 할 수있는 함수가 있으면이를 호출하는 NULL것이 모호합니다. 이 문제를 해결하는 방법은 int를 수락하고이라고 가정하면 매우 해킹됩니다 NULL.

template <class T>
class ptr {
    T* p_;
    public:
        ptr(T* p) : p_(p) {}

        template <class U>
        ptr(U* u) : p_(dynamic_cast<T*>(u)) { }

        // Without this ptr<T> p(NULL) would be ambiguous
        ptr(int null) : p_(NULL)  { assert(null == NULL); }
};

에서 C++11당신에 과부하 할 수있을 것이다 nullptr_t그 때문에 ptr<T> p(42);컴파일 타임 오류가 아닌 실행 시간이 될 것입니다 assert.

ptr(std::nullptr_t) : p_(nullptr)  {  }

어떤 경우 NULL로 정의된다 0L?
LF

9

nullptrint포인터 유형 과 같은 정수 유형에는 할당 할 수 없습니다 . 와 같은 내장 포인터 유형 int *ptr또는 같은 스마트 포인터std::shared_ptr<T>

나는 이것이 NULL여전히 중요한 유형 이라고 생각합니다. 왜냐하면 여전히 포인터 뿐만 아니라 초기 값으로도 사용할 수있는 NULL확장 된 매크로 와 같이 정수 유형과 포인터 모두에 할당 될 수 있기 때문 입니다.0int


이 답변은 잘못되었습니다. NULL로 확장 될 수는 없습니다 0.
LF

6

또한 nullptr좋은 오래된 0보다 우수한 또 다른 예 (Wikipedia 예제 제외) 가 있습니까?

예. 또한 프로덕션 코드에서 발생한 (간체 화 된) 실제 예제이기도합니다. 레지스터 너비가 다른 플랫폼으로 크로스 컴파일 할 때 gcc가 경고를 발행 할 수 있었기 때문에 눈에 띄었습니다 (여전히 x86_64에서 x86으로 크로스 컴파일 할 때만 정확히 경고하지는 않습니다 warning: converting to non-pointer type 'int' from NULL).

이 코드 (C ++ 03)를 고려하십시오.

#include <iostream>

struct B {};

struct A
{
    operator B*() {return 0;}
    operator bool() {return true;}
};

int main()
{
    A a;
    B* pb = 0;
    typedef void* null_ptr_t;
    null_ptr_t null = 0;

    std::cout << "(a == pb): " << (a == pb) << std::endl;
    std::cout << "(a == 0): " << (a == 0) << std::endl; // no warning
    std::cout << "(a == NULL): " << (a == NULL) << std::endl; // warns sometimes
    std::cout << "(a == null): " << (a == null) << std::endl;
}

이 출력을 산출합니다 :

(a == pb): 1
(a == 0): 0
(a == NULL): 0
(a == null): 1

nullptr (및 C ++ 11)을 사용할 때 이것이 어떻게 향상되는지 알지 못합니다. pb를 nullptr로 설정하면 사과와 배를 비교하는 동안 첫 번째 비교는 여전히 참으로 평가됩니다. 두 번째 경우는 더 나쁩니다. a를 nullptr과 비교하면 a를 B *로 변환 한 다음 다시 true로 평가됩니다 (bool로 캐스팅되고 expr이 false로 평가되기 전에). 모든 것이 나에게 JavaScript를 생각 나게하고 앞으로 C ++에서 ===를 얻을지 궁금하다 :(
Nils

5

다른 언어에는 유형의 인스턴스 인 예약어가 있습니다. 예를 들어 파이썬 :

>>> None = 5
  File "<stdin>", line 1
SyntaxError: assignment to None
>>> type(None)
<type 'NoneType'>

이것은 실제로 None초기화되지 않았지만 동시에 None == 0거짓 과 같은 비교에 사용 되기 때문에 상당히 가까운 비교 입니다.

반면에 일반 C에서는 항상 0을 반환하는 매크로이므로 항상 유효하지 않은 주소 (AFAIK) NULL == 0이므로 true IIRC를 반환합니다 NULL.


4
NULL0으로 확장되는 매크로이며 포인터에 대한 지속적인 제로 캐스트는 널 포인터를 생성합니다. 널 포인터는 0 일 필요는 없지만 (종종은 그렇습니다), 0은 항상 유효하지 않은 주소는 아니며 포인터에 대한 상수 0 캐스트는 널일 필요는 없으며 널 포인터는 정수는 0 일 필요는 없습니다. 나는 아무것도 잊지 않고 모든 것을 얻었기를 바랍니다. 참조 : c-faq.com/null/null2.html
Samuel Edwin Ward 1

3

표준에 따라 키워드가 지정되므로 키워드입니다. ;-) 최신 공개 초안에 따르면 (n2914)

2.14.7 포인터 리터럴 [lex.nullptr]

pointer-literal:
nullptr

포인터 리터럴은 키워드 nullptr입니다. 유형의 rvalue입니다 std::nullptr_t.

암시 적으로 정수 값으로 변환되지 않기 때문에 유용합니다.


2

int와 char *를 모두 사용하도록 오버로드 된 함수 (f)가 ​​있다고 가정 해 봅시다. C ++ 11 이전에 널 포인터로 호출하고 NULL (예 : 값 0)을 사용하려면 int에 대해 오버로드 된 것을 호출합니다.

void f(int);
void f(char*);

void g() 
{
  f(0); // Calls f(int).
  f(NULL); // Equals to f(0). Calls f(int).
}

이것은 아마도 당신이 원하는 것이 아닙니다. C ++ 11은이를 nullptr로 해결합니다. 이제 다음을 작성할 수 있습니다.

void g()
{
  f(nullptr); //calls f(char*)
}

1

먼저 정교하지 않은 구현을하겠습니다. nullptr_t

struct nullptr_t 
{
    void operator&() const = delete;  // Can't take address of nullptr

    template<class T>
    inline operator T*() const { return 0; }

    template<class C, class T>
    inline operator T C::*() const { return 0; }
};

nullptr_t nullptr;

nullptr리턴하는 유형 Resolver 관용구 의 미묘한 예제로, 할당 된 인스턴스의 유형에 따라 올바른 유형의 널 포인터를 자동으로 추론합니다.

int *ptr = nullptr;                // OK
void (C::*method_ptr)() = nullptr; // OK
  • 위에서 할 수 있듯이 nullptr정수 포인터에 할당 int되면 템플릿 변환 함수 의 유형 인스턴스가 생성됩니다. 그리고 메소드 포인터도 마찬가지입니다.
  • 이런 방식으로 템플릿 기능을 활용하여 매번 새로운 유형 할당이라는 적절한 유형의 null 포인터를 만듭니다.
  • nullptr값이 0 인 정수 리터럴과 마찬가지로 & 연산자로 삭제 한 주소를 사용할 수 없습니다.

왜 우리가 nullptr먼저 필요한가?

  • NULL아래와 같이 전통적인 문제가 있음을 알 수 있습니다.

1️⃣ 암시 적 변환

char *str = NULL; // Implicit conversion from void * to char *
int i = NULL;     // OK, but `i` is not pointer type

2️⃣ 함수 호출 모호성

void func(int) {}
void func(int*){}
void func(bool){}

func(NULL);     // Which one to call?
  • 컴파일하면 다음과 같은 오류가 발생합니다.
error: call to 'func' is ambiguous
    func(NULL);
    ^~~~
note: candidate function void func(bool){}
                              ^
note: candidate function void func(int*){}
                              ^
note: candidate function void func(int){}
                              ^
1 error generated.
compiler exit status 1

3️⃣ 생성자 과부하

struct String
{
    String(uint32_t)    {   /* size of string */    }
    String(const char*) {       /* string */        }
};

String s1( NULL );
String s2( 5 );
  • 이러한 경우 명시 적 캐스트가 필요합니다 (예 :  String s((char*)0)).

0

0은 포인터의 캐스트 프리 이니셜 라이저로 사용할 수있는 유일한 정수 값이었습니다. 캐스트없이 다른 정수 값으로 포인터를 초기화 할 수 없습니다. 0을 정수 리터럴과 구문 적으로 유사한 consexpr 싱글 톤으로 간주 할 수 있습니다. 모든 포인터 또는 정수를 시작할 수 있습니다. 그러나 놀랍게도, 고유 한 유형이 없다는 것을 알게 될 것입니다 int. 그렇다면 0은 어떻게 포인터를 초기화 할 수 있고 1은 불가능합니까? 실용적인 대답은 포인터 null 값을 정의하는 수단이 필요하고 포인터로의 암시 적 직접 변환 int은 오류가 발생하기 쉽다는 것입니다. 따라서 0은 선사 시대에서 진짜 괴물 괴물이되었습니다. nullptr포인터를 초기화하기 위해 null 값의 실제 싱글 톤 constexpr 표현으로 제안되었습니다. 정수를 직접 초기화하는 데 사용할 수 없으며 NULL0 으로 정의하는 데 관련된 모호성을 제거합니다. nullptrstd 구문을 사용하여 라이브러리로 정의 할 수 있지만 의미 적으로 누락 된 핵심 구성 요소 인 것으로 보입니다. 일부 라이브러리가로 정의하기로 결정하지 않는 한, NULL이제는 더 이상 사용되지 않습니다 .nullptrnullptr


-1

LLVM 헤더는 다음과 같습니다.

// -*- C++ -*-
//===--------------------------- __nullptr --------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef _LIBCPP_NULLPTR
#define _LIBCPP_NULLPTR

#include <__config>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#pragma GCC system_header
#endif

#ifdef _LIBCPP_HAS_NO_NULLPTR

_LIBCPP_BEGIN_NAMESPACE_STD

struct _LIBCPP_TEMPLATE_VIS nullptr_t
{
    void* __lx;

    struct __nat {int __for_bool_;};

    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t() : __lx(0) {}
    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t(int __nat::*) : __lx(0) {}

    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR operator int __nat::*() const {return 0;}

    template <class _Tp>
        _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
        operator _Tp* () const {return 0;}

    template <class _Tp, class _Up>
        _LIBCPP_INLINE_VISIBILITY
        operator _Tp _Up::* () const {return 0;}

    friend _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR bool operator==(nullptr_t, nullptr_t) {return true;}
    friend _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR bool operator!=(nullptr_t, nullptr_t) {return false;}
};

inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t __get_nullptr_t() {return nullptr_t(0);}

#define nullptr _VSTD::__get_nullptr_t()

_LIBCPP_END_NAMESPACE_STD

#else  // _LIBCPP_HAS_NO_NULLPTR

namespace std
{
    typedef decltype(nullptr) nullptr_t;
}

#endif  // _LIBCPP_HAS_NO_NULLPTR

#endif  // _LIBCPP_NULLPTR

(빠른 거래로 많은 것을 발견 할 수 있습니다 grep -r /usr/include/*`)

튀어 나온 한 가지는 연산자 *과부하입니다 (0을 반환하는 것은 segfaulting보다 훨씬 친숙합니다 ...). 또 다른 점은 주소를 저장와 호환 보이지 않는 것입니다 전혀 . 이것은 어떻게 void *를 슬링하고 센티넬 값으로 NULL 포인터를 일반 포인터에 전달하는 것과 비교할 때 "절대 잊지 않을 것입니다. 폭탄이 될 수 있습니다".


-2

NULL은 0 일 필요는 없습니다. 항상 NULL을 사용하고 0을 사용하지 않는 한 NULL은 임의의 값이 될 수 있습니다. 플랫 메모리를 가진 폰 뉴만 마이크로 컨트롤러를 프로그래밍한다고 가정하면, 인터럽트 벡터는 0입니다. NULL이 0이고 NULL 포인터에 무언가가 기록되면 마이크로 컨트롤러가 충돌합니다. NULL이 1024라고 말하고 1024에 예약 변수가 있으면 쓰기가 충돌하지 않으며 프로그램 내부에서 NULL 포인터 할당을 감지 할 수 있습니다. 이것은 PC에서는 무의미하지만 우주 탐사선, 군사 또는 의료 장비의 경우 충돌하지 않는 것이 중요합니다.


2
메모리에서 널 포인터의 실제 값은 0이 아닐 수 있지만 C (및 C ++) 표준에서는 컴파일러에서 정수 0 리터럴을 널 포인터로 변환해야합니다.
bzim
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.