Programing

nullptr은 정확히 무엇입니까?

lottogame 2020. 2. 10. 21:59
반응형

nullptr은 정확히 무엇입니까?


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

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

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

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

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

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

또한 nullptr좋은 예보다 우월한 또 다른 예가 0있습니까?


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

이것은 놀라운 일이 아닙니다. 둘 다 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*)합니다.


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

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

다른 참고 문헌 :


둘 이상의 유형에 대한 포인터를 수신 할 수있는 함수가 있으면이를 호출하는 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)  {  }

왜 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이 특별한 의미를 갖는 데 사용되지 않습니다. "


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

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


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

>>> 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.


또한 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

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

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

pointer-literal:
nullptr

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

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


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*)
}

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


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 포인터를 일반 포인터에 전달하는 것과 비교할 때 "절대 잊지 않을 것입니다. 폭탄이 될 수 있습니다".


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

참고 URL : https://stackoverflow.com/questions/1282295/what-exactly-is-nullptr



반응형