포인터가 기본적으로 NULL로 초기화되지 않는 이유는 무엇입니까?


118

누군가 포인터가 초기화되지 않은 이유를 설명해 주 NULL시겠습니까?
예:

  void test(){
     char *buf;
     if (!buf)
        // whatever
  }

프로그램은 bufnull이 아니기 때문에 if 내부로 들어 가지 않습니다.

나는 왜 어떤 경우에 쓰레기가있는 변수, 특히 메모리의 쓰레기를 다루는 포인터가 필요한지 알고 싶습니다.


13
음, 기본 유형은 초기화되지 않은 상태로 남아 있기 때문입니다. 그래서 나는 당신의 "진짜"질문을 가정합니다. 왜 기본 유형이 초기화되지 않습니까?
GManNickG 2009

11
"buf가 null이 아니기 때문에 프로그램은 if 내부로 들어 가지 않습니다." 정답이 아닙니다. buf 무엇인지 모르기 때문에 그것이 무엇인지 알 수 없습니다 .
Drew Dormann

Java와 달리 C ++는 개발자에게 더 많은 책임을 부여합니다.
Rishi

정수, 포인터, () 생성자를 사용하는 경우 기본값은 0입니다.
Erik Aronesty 2015 년

C ++를 사용하는 사람이 자신이하는 일을 알고 있다는 가정 때문에 , 스마트 포인터 위에 원시 포인터를 사용하는 사람은 자신이하는 일을 (더욱 더) 알고 있습니다!
Lofty Lion

답변:


161

우리 모두는 포인터 (및 기타 POD 유형)가 초기화되어야한다는 것을 알고 있습니다.
그러면 질문은 '누가 초기화해야 하는가'가됩니다.

기본적으로 두 가지 방법이 있습니다.

  • 컴파일러는이를 초기화합니다.
  • 개발자가 초기화합니다.

컴파일러가 개발자가 명시 적으로 초기화하지 않은 변수를 초기화했다고 가정 해 보겠습니다. 그런 다음 변수 초기화가 사소한 일이 아니고 개발자가 선언 지점에서이를 수행하지 않은 이유는 몇 가지 작업을 수행 한 다음 할당해야하기 때문입니다.

이제 컴파일러가 변수를 NULL로 초기화하는 코드에 추가 명령을 추가 한 다음 나중에 올바른 초기화를 수행하기 위해 개발자 코드가 추가되는 상황이 발생했습니다. 또는 다른 조건에서는 변수가 잠재적으로 사용되지 않습니다. 많은 C ++ 개발자는 추가 명령을 지불하고 두 가지 조건 모두에서 비명을 질렀습니다.

시간 문제가 아닙니다. 그러나 또한 공간. 두 리소스 모두 프리미엄이 있고 개발자도 포기하고 싶지 않은 많은 환경이 있습니다.

그러나 : 강제 초기화의 효과를 시뮬레이션 할 수 있습니다. 대부분의 컴파일러는 초기화되지 않은 변수에 대해 경고합니다. 그래서 저는 항상 경고 수준을 가능한 최고 수준으로 설정합니다. 그런 다음 컴파일러에게 모든 경고를 오류로 처리하도록 지시하십시오. 이러한 조건에서 대부분의 컴파일러는 초기화되지 않았지만 사용 된 변수에 대해 오류를 생성하여 코드 생성을 방지합니다.


5
Bob Tabor는 "너무 많은 사람들이 초기화에 대해 충분히 생각하지 않았습니다!"모든 변수를 자동으로 초기화하는 것은 '친숙'하지만 시간이 걸리고 느린 프로그램은 '불친절'합니다. 발견 된 임의의 가비지 malloc을 표시하는 스프레드 시트 또는 편집기는 허용되지 않습니다. C, 숙련 된 사용자를위한 날카로운 도구 (잘못 사용하면 위험 함)는 자동 변수를 초기화하는 데 시간이 걸리지 않아야합니다. 변수를 초기화하는 훈련 휠 매크로가 될 수 있지만, 많은 사람들은 일어 서서주의를 기울이고 약간의 피를 흘리는 것이 더 낫다고 생각합니다. 꼬집음으로, 당신은 연습하는 방식으로 일합니다. 그러니 원하는대로 연습하세요.
Bill IV

2
누군가 초기화를 모두 수정하면 얼마나 많은 버그를 피할 수 있는지 놀랄 것입니다. 컴파일러 경고가 아니라면 지루한 작업이 될 것입니다.
Jonathan Henson

4
@Loki, 나는 당신의 요점을 따르는 데 어려움을 겪고 있습니다. 나는 당신의 답변이 도움이되었다고 칭찬하려고 노력한 것뿐입니다. 그렇지 않다면 죄송합니다.
Jonathan Henson

3
포인터가 먼저 NULL로 설정된 다음 값으로 설정되면 컴파일러가이를 감지하고 첫 번째 NULL 초기화를 최적화 할 수 있어야합니다.
Korchkidu

1
@Korchkidu : 가끔. 그러나 주요 문제 중 하나는 기본값이 사용하기에 완벽하지 않다는 것을 알 수 없기 때문에 초기화하는 것을 잊었다 고 경고 할 방법이 없다는 것입니다.
Deduplicator

41

TC ++ PL에서 Bjarne Stroustrup 인용 (Special Edition p.22) :

기능의 구현은이를 필요로하지 않는 프로그램에 상당한 오버 헤드를 부과해서는 안됩니다.


옵션도 제공하지 않습니다. 그것은 보인다
Jonathan

8
@ Jonathan은 포인터를 null 또는 0으로 초기화하는 것을 방해하지 않습니다.
stefanB 2009

8
예,하지만 Stroustrup은 포인터를 0으로 초기화하여 성능보다는 프로그램의 정확성을 선호하는 기본 구문을 만들 수 있으며 프로그래머가 포인터가 초기화되지 않도록 명시 적으로 요청해야합니다. 결국 대부분의 사람들은 전체 프로그램에서 버그를 수정하는 것보다 적은 양의 코드를 최적화하는 것이 일반적으로 더 쉽다는 이유로 빠르지 만 잘못된 것보다 정확하지만 느린 것을 선호합니다. 특히 괜찮은 컴파일러로 많은 것을 할 수있을 때.
Robert Tuck

1
호환성을 깨지 않습니다. 이 아이디어는 "int * x = __uninitialized"(기본적으로 안전, 의도에 따른 속도)와 함께 고려되었습니다.
MSalters

4
나는 무엇을 좋아합니다 D. 초기화를 원하지 않는 경우이 구문 float f = void;또는 int* ptr = void;. 이제 기본적으로 초기화되지만 정말로 필요한 경우 컴파일러가 수행하는 것을 중지 할 수 있습니다.
deft_code 2010-07-07

23

초기화에는 시간이 걸리기 때문입니다. 그리고 C ++에서 변수로 가장 먼저해야 할 일은 명시 적으로 초기화하는 것입니다.

int * p = & some_int;

또는:

int * p = 0;

또는:

class A {
   public:
     A() : p( 0 ) {}  // initialise via constructor
   private:
     int * p;
};

1
k, 초기화에 시간이 걸리고 여전히 원하는 경우 수동으로 설정하지 않고 포인터를 null로 만드는 방법이 있습니까? 봐, 내가 그것을 고치고 싶지 않아서가 아니라, 내가 그들의 주소에 쓰레기와 함께 단일 포인터를 사용하지 않을 것 같기 때문에
Jonathan

1
클래스 생성자에서 클래스 멤버를 초기화합니다. 이것이 C ++의 작동 방식입니다.

3
@Jonathan :하지만 null도 쓰레기입니다. 널 포인터로는 유용한 일을 할 수 없습니다. 하나를 역 참조하는 것도 오류입니다. null이 아닌 적절한 값으로 포인터를 만듭니다.
DrPizza 2009

2
Nnull에 대한 apointer를 초기화하는 것은 현명한 일이 될 수 있으며, null 포인터에 대해 수행 할 수있는 몇 가지 작업이 있습니다-테스트 할 수 있고 삭제를 호출 할 수 있습니다.

4
명시 적으로 초기화하지 않고 포인터를 사용하지 않을 경우 값을 부여하기 전에 포함 된 내용은 중요하지 않으며 사용한만큼만 지불하는 C 및 C ++ 원칙에 따라 수행되지 않습니다. 자동으로. 허용 가능한 기본값 (일반적으로 널 포인터)이있는 경우이를 초기화해야합니다. 초기화하거나 초기화되지 않은 상태로 둘 수 있습니다.
David Thornley

20

C ++의 모토 중 하나는 다음과 같습니다.


당신은 당신이 사용하지 않는 것에 대해 지불하지 않습니다


바로 이러한 이유로, operator[]vector인덱스가 예를 들어, 범위 외에있는 경우 클래스는 확인하지 않습니다.


12

역사적 이유로, 주로 이것이 C에서 수행되는 방식이기 때문입니다. C에서 그렇게 수행되는 이유는 또 다른 질문입니다. 그러나 제로 오버 헤드 원칙 이이 설계 결정에 어떻게 든 관련 되었다고 생각합니다 .


C는 메모리 (포인터라고도 함)에 쉽게 접근 할 수있는 저수준 언어로 간주되므로 원하는 것을 자유롭게 할 수 있고 모든 것을 초기화하여 오버 헤드를 부과하지 않습니다. BTW 사용하기 전에 모든 메모리를 0으로 초기화 한 Linux 기반 모바일 플랫폼에서 작업했기 때문에 플랫폼에 의존한다고 생각합니다. 그래서 모든 변수는 0으로 설정됩니다.
stefanB

8

게다가, 우리는 당신이 그것을 날려 버릴 때에 대한 경고를 가지고 있습니다 : 당신의 컴파일러에 따라 "값이 할당되기 전에 사용될 수 있습니다"또는 유사한 동사.

경고와 함께 컴파일하는 거 맞죠?


그리고 그것은 단지 가능한 컴파일러가 결함이있을 수 있습니다 추적한다는 acknowlegement한다.
Deduplicator

6

변수가 초기화되지 않는 것이 합리적 일 수있는 상황이 거의없고 기본 초기화의 비용이 적습니다. 왜 그렇게합니까?

C ++는 C89가 아닙니다. C조차도 C89가 아닙니다. 선언과 코드를 혼합 할 수 있으므로 초기화 할 적절한 값이있을 때까지 선언을 연기해야합니다.


2
그런 다음 컴파일러의 설정 루틴에 의해 한 번, 사용자의 프로그램에 의해 다시 한 번 모든 값을 두 번 작성해야합니다. 일반적으로 큰 문제는 아니지만 추가됩니다 (예 : 1 백만 개의 항목 배열을 만드는 경우). 자동 초기화를 원하는 경우 언제든지이를 수행하는 고유 한 유형을 만들 수 있습니다. 그러나 이렇게하면 원하지 않는 경우 불필요한 오버 헤드를 허용하지 않아도됩니다.
Jeremy Friesner

3

포인터는 또 다른 유형입니다. int, char또는 다른 POD 유형 을 생성하면 0으로 초기화되지 않는데 왜 포인터가 필요합니까? 이것은 이와 같은 프로그램을 작성하는 사람에게 불필요한 오버 헤드로 간주 될 수 있습니다.

char* pBuf;
if (condition)
{
    pBuf = new char[50];
}
else
{
    pBuf = m_myMember->buf();
}

초기화 할 것이라는 것을 알고 있다면 pBuf, 메서드의 맨 위에서 처음 만들 때 프로그램에 비용이 발생하는 이유는 무엇입니까? 이것이 제로 오버 헤드 원칙입니다.


1
반면에 char * pBuf = condition? new char [50] : m_myMember-> buf (); 그것은 구문의 문제와 효율성에 더 가깝지만 그럼에도 불구하고 나는 당신에게 동의합니다.
the_drow dec. 092009-12-15

1
@the_drow : 글쎄요, 그런 재 작성이 불가능하도록 더 복잡하게 만들 수 있습니다.
Deduplicator

2

항상 NULL로 초기화되는 포인터를 원하는 경우 C ++ 템플릿을 사용하여 해당 기능을 에뮬레이트 할 수 있습니다.

template<typename T> class InitializedPointer
{
public:
    typedef T       TObj;
    typedef TObj    *PObj;
protected:
    PObj        m_pPointer;

public:
    // Constructors / Destructor
    inline InitializedPointer() { m_pPointer=0; }
    inline InitializedPointer(PObj InPointer) { m_pPointer = InPointer; }
    inline InitializedPointer(const InitializedPointer& oCopy)
    { m_pPointer = oCopy.m_pPointer; }
    inline ~InitializedPointer() { m_pPointer=0; }

    inline PObj GetPointer() const  { return (m_pPointer); }
    inline void SetPointer(PObj InPtr)  { m_pPointer = InPtr; }

    // Operator Overloads
    inline InitializedPointer& operator = (PObj InPtr)
    { SetPointer(InPtr); return(*this); }
    inline InitializedPointer& operator = (const InitializedPointer& InPtr)
    { SetPointer(InPtr.m_pPointer); return(*this); }
    inline PObj operator ->() const { return (m_pPointer); }
    inline TObj &operator *() const { return (*m_pPointer); }

    inline bool operator!=(PObj pOther) const
    { return(m_pPointer!=pOther); }
    inline bool operator==(PObj pOther) const
    { return(m_pPointer==pOther); }
    inline bool operator!=(const InitializedPointer& InPtr) const
    { return(m_pPointer!=InPtr.m_pPointer); }
    inline bool operator==(const InitializedPointer& InPtr) const
    { return(m_pPointer==InPtr.m_pPointer); }

    inline bool operator<=(PObj pOther) const
    { return(m_pPointer<=pOther); }
    inline bool operator>=(PObj pOther) const
    { return(m_pPointer>=pOther); }
    inline bool operator<=(const InitializedPointer& InPtr) const
    { return(m_pPointer<=InPtr.m_pPointer); }
    inline bool operator>=(const InitializedPointer& InPtr) const
    { return(m_pPointer>=InPtr.m_pPointer); }

    inline bool operator<(PObj pOther) const
    { return(m_pPointer<pOther); }
    inline bool operator>(PObj pOther) const
    { return(m_pPointer>pOther); }
    inline bool operator<(const InitializedPointer& InPtr) const
    { return(m_pPointer<InPtr.m_pPointer); }
    inline bool operator>(const InitializedPointer& InPtr) const
    { return(m_pPointer>InPtr.m_pPointer); }
};

1
내가 이것을 구현한다면, 복사기 나 할당 작업에 신경 쓰지 않을 것입니다. 기본값은 꽤 괜찮습니다. 그리고 당신의 소멸자는 무의미합니다. 물론 일부 상황에서보다 적은 연산자 등을 사용하여 포인터를 테스트 할 수도 있으므로 포인터를 제공해야합니다.

OK, 구현하기가 사소한 것보다 적습니다. 객체가 범위를 벗어나지 만 (즉, 함수의 하위 범위 내에 정의 된 로컬) 여전히 스택에서 공간을 차지하는 경우 메모리가 쓰레기에 대한 매달린 포인터로 남아 있지 않도록 소멸자가 있습니다. 하지만 진지하게, 나는 이것을 5 분 안에 썼다. 완벽하다는 의미는 아닙니다.
Adisak 2009

OK는 모든 비교 연산자를 추가했습니다. 기본 재정의는 중복 될 수 있지만 예제이므로 여기에 명시 적으로 있습니다.
Adisak 2009

1
수동으로 설정하지 않고 모든 포인터를 null로 만드는 방법을 이해할 수 없었습니다. 여기서 무엇을했는지 설명해 주시겠습니까?
Jonathan

1
@Jonathan : 이것은 기본적으로 포인터를 null로 설정하는 것 외에는 아무것도하지 않는 "스마트 포인터"입니다. 대신 IE Foo *a를 사용합니다 InitializedPointer<Foo> a.- Foo *a=0타이핑이 적기 때문에 순수하게 학문적 인 연습을 합니다. 그러나 위의 코드는 교육적인 관점에서 매우 유용합니다. 약간의 수정 ( "placeholding"ctor / dtor 및 할당 작업에 대한)으로 inc /를 추가하여 범위 포인터 (소멸자에서 해제 됨) 및 참조 계수 포인터를 포함한 다양한 유형의 스마트 포인터로 쉽게 확장 할 수 있습니다. m_pPointer가 설정되거나 지워질 때 dec 작업.
Adisak 2009

2

정적 데이터는 0으로 초기화됩니다 (달리 언급하지 않는 한).

그리고 예, 항상 가능한 한 늦게 초기 값으로 변수를 선언해야합니다. 같은 코드

int j;
char *foo;

읽을 때 알람 벨을 울려 야합니다. 100 % 합법적이기 때문에 어떤 보푸라기 라도 잉어에 대해 설득 할 수 있는지 모르겠습니다 .


이것이 보장됩니까, 아니면 오늘날의 컴파일러가 사용하는 일반적인 관행입니까?
gha.st

1
정적 변수는 0으로 초기화되어 포인터에 대해서도 올바른 작업을 수행합니다 (즉, 모든 비트가 0이 아닌 NULL로 설정). 이 동작은 표준에 의해 보장됩니다.
Alok Singhal

1
정적 데이터를 0으로 초기화하는 것은 C 및 C ++ 표준에 의해 보장되며 일반적인 관행이 아닙니다
groovingandi

1
어떤 사람들은 스택이 잘 정렬되어 있는지 확인하기 위해 함수 상단에 모든 변수를 미리 선언하기 때문일까요? 아마도 그들은 이것을 요구하는 ac 방언으로 쓰고 있습니까?
KitsuneYMG 2009

1

또 다른 가능한 이유는 링크 타임에 포인터에 주소가 주어 지지만 포인터의 간접 주소 지정 / 역 참조는 프로그래머의 책임이기 때문입니다. 일반적으로 컴파일러는 그다지 신경 쓰지 않지만 포인터를 관리하고 메모리 누수가 발생하지 않는지 확인하는 부담은 프로그래머에게 전달됩니다.

간단히 말해서 링크 타임에 포인터 변수에 주소가 주어진다는 의미에서 초기화됩니다. 위의 예제 코드에서 이는 충돌하거나 SIGSEGV를 생성하도록 보장됩니다.

어떤 시도가없이 역 참조 할 경우 정신을 위해, 항상 그런 식으로, NULL 포인터를 초기화 malloc하거나 new프로그램의 잘못이 동작하는 이유에 프로그래머 의지 단서.

이것이 도움이되고 이해되기를 바랍니다.


0

글쎄, 만약 C ++가 포인터를 초기화했다면, "C ++는 C보다 느리다"라고 불평하는 C 사람들은 진짜로 매달릴 것이다;)


그게 내 이유가 아닙니다. 제 이유는 하드웨어에 512 바이트의 ROM과 128 바이트의 RAM이 있고 0에 대한 추가 명령이있는 경우 포인터는 전체 프로그램에서 상당히 많은 비율을 차지하는 1 바이트라는 것입니다. 그 바이트가 필요합니다!
Jerry Jeremiah

0

C ++는 C 배경에서 비롯되었으며 여기에서 돌아 오는 몇 가지 이유가 있습니다.

C는 C ++보다 훨씬 더 어셈블리 언어 대체입니다. 당신이 지시하지 않은 것은 아무것도하지 않습니다. 따라서 : NULL을 원할 경우 수행하십시오!

또한 C와 같은 베어 메탈 언어에서 null을 사용하면 자동으로 일관성 질문이 나타납니다. malloc을 사용하면 자동으로 0으로 설정해야합니까? 스택에 생성 된 구조체는 어떻습니까? 모든 바이트를 0으로해야합니까? 전역 변수는 어떻습니까? "(* 0x18);"과 같은 문장은 어떻습니까? 그렇다면 메모리 위치 0x18이 제로화되어야 함을 의미하지 않습니까?


실제로 C에서 모두 0 메모리를 할당하려면을 사용할 수 있습니다 calloc().
David Thornley

1
내 포인트 - 당신은 당신이 할 수있는 그것을 할 싶어하지만, 그것은 자동적으로 당신을 위해하지 않은 경우
gha.st

0

당신이 말하는이 포인터는 무엇입니까?

예외 안전을 위해, 항상 사용 auto_ptr, shared_ptr, weak_ptr과 다른 변종.
좋은 코드의 특징은 단일 호출을 포함하지 않는 것 delete입니다.


3
C ++ 11, 피하다 이후 auto_ptr및 대체 unique_ptr.
Deduplicator

-2

오 소년. 진짜 대답은 포인터의 기본 초기화 인 메모리를 제로화하기 쉽다는 것입니다. 객체 자체를 초기화하는 것과도 관련이 없습니다.

대부분의 컴파일러가 가장 높은 수준에서 제공하는 경고를 고려하면 가장 높은 수준에서 프로그래밍하고 오류로 처리하는 것을 상상할 수 없습니다. 생성 된 엄청난 양의 코드에서 버그를 하나도 저장하지 않았기 때문에 이것을 권장 할 수 없습니다.


포인터가되지 않을 경우 기대NULL그에게 초기화하는 것은 단지 많은 오류가 발생합니다.
Deduplicator
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.