널 포인터에 주소 0이 사용되는 이유는 무엇입니까?


121

C (또는 C ++)에서 포인터는 값이 0이면 특별합니다. 포인터를 다시 해제하는 것은 위험하지 않기 때문에 메모리를 해제 한 후 포인터를 0으로 설정하는 것이 좋습니다. malloc을 호출 할 때 메모리를 얻을 수없는 경우 값이 0 인 포인터를 반환합니다. if (p != 0)전달 된 포인터가 유효한지 확인하기 위해 항상 사용 합니다.

그러나 메모리 주소 지정이 0에서 시작하기 때문에 0이 다른 주소처럼 유효한 주소가 아닙니까? 그렇다면 null 포인터를 처리하는 데 0을 어떻게 사용할 수 있습니까? 대신 음수가 null이 아닌 이유는 무엇입니까?


편집하다:

좋은 답변이 많이 있습니다. 내 마음이 해석하는대로 표현한 답변을 요약하고, 내가 오해하면 커뮤니티가 나를 고쳐 주길 바란다.

  • 프로그래밍의 다른 모든 것과 마찬가지로 추상화입니다. 주소 0과 실제로 관련이없는 상수 일뿐입니다. C ++ 0x는 키워드를 추가하여이를 강조합니다 nullptr.

  • 그것은 주소 추상화조차도 아니고, C 표준에 의해 지정된 상수이고 컴파일러는 그것이 "실제"주소와 같지 않다는 것을 확인하고 0이 아닌 경우 다른 널 포인터와 같으면 다른 숫자로 변환 할 수 있습니다. 플랫폼에 사용할 수있는 최고의 가치.

  • 초창기의 경우처럼 추상화가 아닌 경우에는 주소 0이 시스템에서 사용되며 프로그래머에게 제한이 없습니다.

  • 내 음수 제안은 약간 거친 브레인 스토밍이었습니다. 주소에 부호있는 정수를 사용하는 것은 널 포인터 (-1 또는 기타)를 제외하고 값 공간이 유효한 주소를 만드는 양의 정수와 낭비되는 음수로 균등하게 분할된다는 것을 의미한다면 약간 낭비입니다.

  • 어떤 숫자가 항상 데이터 유형으로 표현 될 수 있다면 0입니다. (아마도 1도 마찬가지입니다. 부호가없는 경우 0 또는 1이 될 1 비트 정수, 부호가있는 경우 부호있는 비트 만, 또는 2 비트 정수가 [-2, 1]이됩니다.하지만 0은 null이고 1은 메모리에서 액세스 가능한 유일한 바이트입니다.)

여전히 내 마음 속에 해결되지 않은 것이 있습니다. 특정 고정 주소에 대한 스택 오버플로 질문 포인터 는 null 포인터에 대한 0이 추상화 인 경우에도 다른 포인터 값이 반드시 필요한 것은 아니라는 것을 알려줍니다. 이로 인해 다른 스택 오버플로 질문이 게시 됩니다. 주소 0에 액세스하고 싶을 수 있습니까? .


11
C와 C ++에서 일반적인 관용구 로 쉽게 변경할 if (p != 0)if (p)있지만 Java를 사용하면 습관에서 벗어나야합니다.
Paul Tomblin

14
두 번 삭제하면 코드가 잘못되었음을 의미합니다. 나는 포인터를 null로 설정하지 않는 것이 좋습니다. 그래야 문제를 억제하지 않고 문제를 해결할 수 있습니다. 어쨌든 주소가 정수라고 가정하는 실수를 범합니다. 이것은 반드시 사실이 아니며 0 은 구현에 특정한 일부 실제 포인터 값을 나타냅니다 . "부정적인 주소"는 개념적으로 의미가 없습니다.
GManNickG 2010 년

6
@GMan : .NET과 같이 충돌을 일으킬 수있는 주소에 포인터를 설정하는 것이 좋습니다 0xDEADBEEF.
Billy ONeal 2010 년

5
결코 죽지 않을 질문!

8
@Noah : 포인트는 null로 설정-> 프로그래밍 오류 숨기기, null로 설정하지 않음-> 프로그래밍 오류 찾기. 나는 당신에 대해 모르지만 내 코드를 수정하고 싶습니다.
GManNickG 2010 년

답변:


65

2 점 :

널 포인터에 대한 유일한 요구 사항은 다음과 같습니다.

  • 실제 객체에 대한 포인터와 같지 않음을 비교하는 것이 보장됩니다.
  • 두 개의 널 포인터는 동일하게 비교됩니다 (C ++는 동일한 유형에 대한 포인터 만 보유 할 필요가 있도록이를 다듬습니다).

12
+1 단지 역사적 이유로 0이 선택되었다고 생각합니다. (0은 대부분의 경우 시작 주소이고 유효하지 않은 주소입니다.) 물론 일반적으로 이러한 가정은 항상 사실은 아니지만 0은 꽤 잘 작동합니다.
GManNickG 2010 년

8
공간도 기여한 요인 일 수 있습니다. C가 처음 개발되었을 때 메모리는 지금보다 훨씬 더 비쌌습니다. 숫자 0은 XOR 명령어를 사용하거나 즉시 값을로드 할 필요없이 편리하게 계산할 수 있습니다. 아키텍처에 따라 잠재적으로 공간을 절약 할 수 있습니다.
Sparky

6
@GMan-당신이 맞습니다. 초기 CPU에서 메모리 주소 0은 특별했으며 실행중인 소프트웨어의 액세스에 대한 하드웨어 보호 기능이있었습니다 (경우에 따라 재설정 벡터의 시작이었으며이를 수정하면 CPU가 재설정되거나 시작되지 않을 수 있음). 프로그래머는이 하드웨어 보호를 소프트웨어에서 오류 감지의 한 형태로 사용하여 CPU의 주소 디코딩 논리가 CPU 명령을 사용하는 대신 초기화되지 않았거나 잘못된 포인터를 검사하도록했습니다. 주소 0의 목적이 변경 되었음에도 불구하고이 협약은 오늘날까지 남아 있습니다.
bta 2010 년

10
Minix 16 비트 컴파일러는 NULL에 0xFFFF를 사용했습니다.
Joshua

3
많은 임베디드 시스템에서 0은 유효한 주소입니다. 값 -1 (모든 비트 1)도 유효한 주소입니다. 데이터가 주소 0에서 시작하면 ROM의 체크섬을 계산하기 어렵습니다. :-(
Thomas Matthews

31

역사적으로 0에서 시작하는 주소 공간은 항상 ROM이었고 일부 운영 체제 또는 낮은 수준의 인터럽트 처리 루틴에 사용되었습니다. 특히 주소 0에 아무것도 할당하지 마십시오.


6
그게 다야. 이것은 역사적 관습에 따라 첫 번째 주소가 인터럽트 핸들러에 사용되었으므로 일반 프로그램에서는 사용할 수 없습니다. 또한 0은 "비어 있음"으로 값이 없거나 포인터가없는 것으로 해석 할 수 있습니다.
TomTom

15

IIRC, "null 포인터"값은 0이 보장되지 않습니다. 컴파일러는 0을 시스템에 적합한 "null"값으로 변환합니다 (실제로는 항상 0이지만 반드시 그런 것은 아님). 포인터를 0과 비교할 때마다 동일한 변환이 적용됩니다. 포인터를 서로 및이 특수 값 0에 대해서만 비교할 수 있기 때문에 프로그래머가 시스템의 메모리 표현에 대해 알지 못하도록 차단합니다. 왜 그들이 42 대신 0을 선택했는지에 관해서는 대부분의 프로그래머가 0에서 계산하기 시작했기 때문이라고 추측 할 것입니다 :) (또한 대부분의 시스템에서 0은 첫 번째 메모리 주소이며 편리하기를 원했습니다. 제가 설명하는 것처럼 번역을 연습하는 일은 거의 발생하지 않습니다.


5
@ 저스틴 : 오해 했군요. 상수 0은 항상 널 포인터입니다. @meador가 말하는 것은 null 포인터 (상수 0으로 표시됨)가 주소 0에 해당하지 않을 수 있다는 것입니다. 일부 플랫폼에서 널 포인터 ( int* p = 0)를 생성하면 값 0xdeadbeef또는 선호하는 다른 값을 포함하는 포인터가 생성 될 수 있습니다 . 0은 널 포인터이지만 널 포인터가 반드시 주소 0에 대한 포인터는 아닙니다. :)
jalf

NULL 포인터는 예약 된 값이며 컴파일러에 따라 모든 비트 패턴이 될 수 있습니다. NULL 포인터가 주소 0을 가리키는 것은 아닙니다.
Sharjeel Aziz

3
그러나 @Jalf, 상수 0 항상 널 포인터 는 아닙니다 . 컴파일러가 플랫폼의 실제 널 포인터 를 채우기를 원할 때 작성 합니다. 실질적으로 말하기, 널 포인터는 일반적으로 수행 하지만, 주소 제로로 대응을하고, 나는 그 이유를 묻는으로 조엘의 질문을 해석한다. 결국 해당 주소에 유효한 메모리 바이트가있을 것입니다. 그러면 유효한 바이트를 재생에서 제거하는 대신 존재하지 않는 바이트의 존재하지 않는 주소를 사용하는 것이 어떻습니까? (저는 Joel이 생각하는 것을 작성하고 있습니다. 제가 스스로 묻는 질문이 아닙니다.)
Rob Kennedy

@Rob : 일종의. 나는 당신이 의미하는 바를 알고 있고 당신이 맞지만 나도 그렇습니다. :) 상수 정수 0은 소스 코드 수준에서 널 포인터를 나타냅니다. null 포인터를 0과 비교하면 true가됩니다. 포인터에 0을 할당하면 해당 포인터가 null로 설정됩니다. 0 널 포인터입니다. 그러나 널 포인터의 실제 메모리 내 표현은 0 비트 패턴과 다를 수 있습니다. (어쨌든 내 댓글은
@Joel

@jalf @Rob 명확하게하기 위해 몇 가지 용어가 필요하다고 생각합니다. :) From §4.10 / 1 : " 널 포인터 상수 는 0으로 평가되는 정수 유형의 정수 상수 표현식 rvalue입니다. 널 포인터 상수는 포인터 유형으로 변환 될 수 있습니다. 결과는 해당 유형 의 널 포인터 값 이고 객체에 대한 포인터 또는 함수 유형에 대한 포인터의 다른 모든 값과 구별됩니다. "
GManNickG 2010 년

15

포인터 컨텍스트에서 상수 0의 의미를 오해하고 있어야합니다.

C 나 C ++ 포인터 모두 "값 0을 가질"수 없습니다. 포인터는 산술 개체가 아닙니다. 그들은 "0"이나 "음수"또는 그 성질의 어떤 것과 같은 숫자 값을 가질 수 없습니다. 따라서 "포인터 ... 값이 0"에 대한 귀하의 진술은 의미가 없습니다.

C & C ++에서 포인터는 예약 된 널 포인터 값을 가질 수 있습니다 . 널 포인터 값의 실제 표현은 "0"과는 관련이 없습니다. 주어진 플랫폼에 절대적으로 적합한 모든 것이 될 수 있습니다. 대부분의 플랫폼에서 null-pointer 값은 실제 제로 주소 값으로 물리적으로 표현되는 것이 사실입니다. 그러나 일부 플랫폼에서 주소 0이 실제로 어떤 목적으로 사용되는 경우 (즉, 주소 0에서 객체를 생성해야 할 수 있음) 이러한 플랫폼의 널 포인터 값은 대부분 다를 수 있습니다. 물리적으로 0xFFFFFFFF주소 값 또는0xBAADBAAD예를 들어 .

그럼에도 불구하고 주어진 플랫폼에서 널 포인터 값이 어떻게 표현되는지에 관계없이 코드에서 계속해서 constant로 널 포인터를 지정 0합니다. 주어진 포인터에 null 포인터 값을 할당하려면 p = 0. 원하는 것을 깨닫고 적절한 null 포인터 값 표현으로 변환하는 것은 컴파일러의 책임 입니다. 예를 들어 주소 값을 0xFFFFFFFF포인터에 넣을 코드로 변환하는 것은 컴파일러의 책임 p입니다.

요컨대, 당신이 0널 포인터 값을 생성하기 위해 소스 코드에서 사용 한다는 사실은 널 포인터 값이 어떻게 든 address에 묶여 있음을 의미하지는 않습니다 0. 0당신이 당신의 소스 코드에서 사용하는 것이이 널 포인터 값으로 "지적"하는 실제 물리적 주소로 전혀 관계가없는 그냥 "문법적"입니다.


3
<quote> 포인터는 산술 개체가 아닙니다. </ quote> 포인터 산술은 C와 C ++에서 매우 잘 정의되어 있습니다. 요구 사항의 일부는 두 포인터가 동일한 합성 내에서 가리키는 것입니다. 널 포인터는 합성물을 가리 키지 않으므로 포인터 산술 표현식에서 사용하는 것은 불법입니다. 예를 들어 (p1 - nullptr) - (p2 - nullptr) == (p1 - p2).
Ben Voigt

5
@Ben Voigt : 언어 사양은 산술 유형 의 개념을 정의합니다 . 내가 말하는 것은 포인터 유형이 산술 유형의 범주에 속하지 않는다는 것입니다. 포인터 산술 은 완전히 관련이없는 다른 이야기이며 단순한 언어 우연입니다.
AnT 2010 년

1
산술 객체를 읽는 사람 은 "산술 연산자의 의미에서"(포인터에서 여러 개를 사용할 수 있음) 또는 "포인터 산술의 의미에서"가 아니라 "산술 유형의 의미에서"를 의미한다는 것을 알고 있어야합니다. 언어 우연에 관한 한, 산술 객체산술 유형 보다 포인터 산술 과 공통된 문자가 더 많습니다 . 동시에 표준은 포인터 값 에 대해 이야기 합니다 . 원래 포스터는 아마 의미 포인터의 표현 정수 가 아닌 포인터 값 , 및 명시 적으로 0으로 표현 될 필요가 없다NULL
벤 보이트

예를 들어, C / C ++ 용어에서 스칼라 객체 라는 용어는 스칼라 유형의 객체에 대한 속기 일뿐입니다 ( POD 객체 = POD 유형의 객체 처럼 ). 나는 산술 객체 라는 용어 를 정확히 같은 방식으로 사용했는데, 이는 산술 유형의 객체를 의미 합니다 . 나는 "누군가"가 그렇게 이해하기를 기대합니다. 항상 설명을 요청할 수없는 사람.
AnT 2010 년

1
나는 (하드웨어에 관한 한) null이 0xffffffff이고 0이 완벽하게 유효한 주소 인 시스템에서 작업했습니다
pm100

8

그러나 메모리 주소 지정이 0에서 시작하기 때문에 0이 다른 주소처럼 유효한 주소가 아닙니까?

일부 / 다 / 모든 운영 체제에서 메모리 주소 0은 어떤면에서 특별합니다. 예를 들어, 종종 유효하지 않거나 존재하지 않는 메모리에 매핑되어 액세스하려고하면 예외가 발생합니다.

대신 음수가 null이 아닌 이유는 무엇입니까?

포인터 값은 일반적으로 부호없는 숫자로 취급된다고 생각합니다. 그렇지 않으면 예를 들어 32 비트 포인터는 4GB가 아닌 2GB의 메모리 만 처리 할 수 ​​있습니다.


4
주소 0이 유효한 주소이고 메모리 보호 기능이없는 장치에서 코딩했습니다. 널 포인터도 모두 0 비트였습니다. 실수로 널 포인터에 썼다면 제로 주소에 있던 OS 설정을 비난했습니다. 일반적으로 웃음은 계속되지 않았습니다.
MM

1
예 : 비보호 모드 x86 CPU에서 예를 들어 주소 0은 인터럽트 벡터 테이블 입니다.
ChrisW

@ChrisW : 비보호 모드 x86에서 주소 0은 특히 0으로 나누기 인터럽트 벡터이며, 일부 프로그램은 쓰기에 대한 합법적 인 이유가있을 수 있습니다.
supercat

사용 가능한 스토리지가 물리적 주소 인 0에서 시작하는 플랫폼에서도 C 구현은 주소 0을 사용하여 주소를 가져 오지 않은 객체를 보유하거나 단순히 메모리의 첫 번째 단어를 사용하지 않은 상태로 둘 수 있습니다. 대부분의 플랫폼에서 0으로 비교는 명령어를 다른 것과 비교하여 저장하므로 스토리지의 첫 번째 단어를 낭비하는 것도 null에 0이 아닌 주소를 사용하는 것보다 저렴합니다. C 표준 (예 : I / O 포트 또는 인터럽트 벡터)에서 다루지 않는 주소의 주소가 null과 같지 않은 것을 비교할 필요가 없습니다.
supercat

... 시스템 프로세스 널 포인터는 다른 것과 다르게 액세스하므로 물리적 위치 0에 대한 액세스가 유용하고 의미있는 시스템에서도 일반적으로 all-bits-zero는 "null"에 대한 좋은 주소입니다.
supercat

5

내 생각에는 더 적은 명령으로 테스트 할 수 있기 때문에 유효하지 않은 포인터를 정의하기 위해 매직 값 0이 선택되었을 것입니다. 일부 기계 언어는 레지스터를로드 할 때 데이터에 따라 자동으로 0 및 부호 플래그를 설정하므로 별도의 비교 명령을 수행하지 않고도 간단한로드 후 분기 명령으로 널 포인터를 테스트 할 수 있습니다.

(대부분의 ISA는로드가 아닌 ALU 명령어에만 플래그를 설정합니다. 일반적으로 C 소스를 구문 분석 할 때 컴파일러를 제외하고는 계산을 통해 포인터를 생성하지 않습니다. 하지 않습니다. 그러나 최소한 임의의 포인터 너비 상수는 필요하지 않습니다. 비교하십시오.)

제가 작업 한 첫 번째 머신 인 Commodore Pet, Vic20 및 C64에서 RAM은 위치 0에서 시작되었으므로 원하는 경우 널 포인터를 사용하여 읽고 쓰는 것이 전적으로 유효했습니다.


3

그냥 컨벤션이라고 생각합니다. 유효하지 않은 포인터를 표시하려면 값이 있어야합니다.

1 바이트의 주소 공간을 잃어 버리면 거의 문제가되지 않습니다.

부정적인 포인터가 없습니다. 포인터는 항상 서명되지 않습니다. 또한 그들이 부정적 일 수 있다면 당신의 관습은 주소 공간의 절반을 잃는다는 것을 의미합니다.


참고 : 실제로 주소 공간이 손실되지는 않습니다. 다음을 수행하여 주소 0에 대한 포인터를 얻을 수 있습니다 char *p = (char *)1; --p;.. 널 포인터에 대한 동작은 표준에 의해 정의되지 않았기 때문에이 시스템은 p실제로 주소 0을 읽고 쓸 수 있고 주소를 제공하기 위해 증분 1등 을 가질 수 있습니다 .
MM

@MattMcNabb : 주소 0이 유효한 하드웨어 주소 인 구현은 주소 0 을 읽고 해당 값을 x에 저장하는 동작을 완벽하게 합법적으로 정의 할 수 있습니다 char x = ((char*)0);. 이러한 코드는 동작을 정의하지 않은 모든 구현에서 정의되지 않은 동작을 생성하지만 표준이 정의되지 않은 동작이라고 말하는 사실은 구현이 수행 할 작업에 대한 자체 사양을 제공하는 것을 금지하지 않습니다.
supercat

@supercat ITYM *(char *)0. 사실이지만 제 제안에 따르면 구현은 *(char *)0다른 null 포인터 작업 의 동작을 정의 할 필요가 없습니다 .
MM

1
@MattMcNabb :의 동작은 char *p = (char*)1; --p;객체의 첫 번째 바이트가 아닌 다른 것에 대한 포인터가로 캐스트 된 후 시퀀스가 ​​수행 된 경우에만 표준에 의해 정의되고 해당 캐스트 intptr_t의 결과가 값 1을 산출 한 경우에만 정의됩니다. , 그리고이 특별한 경우에의 결과는 --p캐스트 될 때 포인터 값 intptr_t이를 산출 한 바이트 앞의 바이트에 대한 포인터를 산출 1합니다.
supercat 2015 년

3

C는 0을 사용하여 널 포인터를 나타내지 만 포인터 자체의 값은 0이 아닐 수 있습니다. 그러나 대부분의 프로그래머는 널 포인터가 실제로 0 인 시스템 만 사용합니다.

하지만 왜 0일까요? 글쎄, 그것은 모든 시스템이 공유하는 하나의 주소입니다. 그리고 종종 낮은 주소는 운영 체제 목적으로 예약되어 있으므로 값은 응용 프로그램에 대한 제한이 없습니다. 실수로 정수 값을 포인터에 할당하면 다른 것과 마찬가지로 0이 될 수 있습니다.


3
이 모든 것의 가능성이 더 높은 이유는 0으로 미리 초기화 된 메모리를 제공하는 것이 저렴하고 해당 메모리의 값이 정수 0, 부동 소수점 0.0 및 널 포인터와 같은 의미있는 것을 나타내는 것이 편리하기 때문입니다. 0 / null로 초기화 된 C의 정적 데이터는 실행 파일의 공간을 차지할 필요가 없으며로드 될 때 0으로 채워진 블록에 매핑됩니다. 0은 기계 언어에서도 특별한 처리를받을 수 있습니다. "0과 같으면 분기"등과 같은 쉬운 제로 비교. MIPS에는 제로 상수 인 더미 레지스터도 있습니다.
카즈

2

역사적으로 응용 프로그램의 메모리 부족은 시스템 리소스로 가득 차있었습니다. 당시에는 0이 기본 null 값이되었습니다.

이것이 현대 시스템에 반드시 해당되는 것은 아니지만, 포인터 값을 메모리 할당이 제공 한 것 외에 다른 것에 설정하는 것은 여전히 ​​나쁜 생각입니다.


2

포인터를 삭제 한 후 null로 설정하지 않는 것에 대한 인수에 대해서는 앞으로 "오류 노출"을 삭제합니다.

만약 당신이 정말로 이것에 대해 정말로 걱정한다면, 더 나은 접근법, 작동이 보장되는 접근법은 assert ()를 활용하는 것입니다 :


...
assert(ptr && "You're deleting this pointer twice, look for a bug?");
delete ptr;
ptr = 0;
...

이를 위해서는 약간의 추가 타이핑과 디버그 빌드 중에 한 번의 추가 검사가 필요하지만 원하는 것을 제공하는 것은 확실합니다. ptr이 '두 번'삭제 될 때 알림. 주석 토론에 제공된 대안은 포인터를 null로 설정하지 않아 충돌이 발생하므로 성공을 보장 할 수 없습니다. 더 나쁜 것은 위와 달리 이러한 "버그"중 하나가 선반에 도달하면 사용자에게 충돌을 일으킬 수 있습니다 (또는 훨씬 더 심각합니다!). 마지막으로이 버전을 사용하면 프로그램을 계속 실행하여 실제로 어떤 일이 발생하는지 확인할 수 있습니다.

나는 이것이 질문에 대한 대답이 아니라는 것을 알고 있지만, 주석을 읽는 누군가가 free () 또는 free ()로 보내질 가능성이 있다면 포인터를 0으로 설정하지 않는 것이 '좋은 습관'으로 간주된다는 결론에 도달 할까 걱정했습니다. 두 번 삭제하십시오. 가능한 경우, 정의되지 않은 동작을 디버깅 도구로 사용하는 것은 결코 좋은 습관이 아닙니다. 궁극적으로 잘못된 포인터를 삭제하여 발생한 버그를 찾아야하는 사람은 아무도 이것을 제안하지 않습니다. 이러한 종류의 오류는 추적하는 데 몇 시간이 걸리며 거의 항상 원래 문제로 추적하기 어려운 완전히 예상치 못한 방식으로 프로그램에 영향을줍니다.


2

많은 운영 체제가 널 포인터 표현에 대해 all-bits-zero를 사용하는 중요한 이유는 이것이 의미 memset(struct_with_pointers, 0, sizeof struct_with_pointers)하고 유사하게 내부의 모든 포인터 struct_with_pointers를 널 포인터로 설정하기 때문입니다. 이것은 C 표준에 의해 보장되지는 않지만 많은 프로그램에서이를 가정합니다.


1

오래된 DEC 머신 (PDP-8, 제 생각에) 중 하나에서 C 런타임은 메모리의 첫 번째 페이지를 메모리로 보호하여 해당 블록의 메모리에 액세스하려고하면 예외가 발생합니다.


PDP-8에는 C 컴파일러가 없습니다. PDP-11에는 메모리 보호 기능이 없었고 VAX는 0에서 NULL 포인터 역 참조를 자동으로 반환하는 것으로 악명이 높습니다. 이것이 어떤 기계를 가리키는 지 잘 모르겠습니다.
fuz

1

센티넬 값의 선택은 임의적이며 실제로 이것은 C ++의 다음 버전 (비공식적으로 "C ++ 0x"로 알려짐, 향후 ISO C ++ 2011로 알려질 가능성이 가장 높음)에서 nullptrnull 값 포인터를 나타내는 키워드 입니다. C ++에서 0 값은 모든 POD 및 기본 생성자가있는 모든 객체에 대한 초기화 표현식으로 사용될 수 있으며, 포인터 초기화의 경우 센티넬 값을 할당하는 특별한 의미가 있습니다. 음수 값을 선택하지 않은 이유는 일반적으로 주소 범위가 0에서 2까지입니다. 2N입니다. 일부 값 N의 -1. 즉, 주소는 일반적으로 부호없는 값으로 처리됩니다. 센티넬 값으로 최대 값을 사용했다면 메모리 크기에 따라 시스템마다 달라야하지만 0은 항상 표현 가능한 주소입니다. 또한 메모리 주소 0은 일반적으로 프로그램에서 사용할 수 없으며 오늘날 대부분의 OS에는 커널의 일부가 메모리의 하위 페이지에로드되어 있으며 이러한 페이지는 일반적으로 다음과 같은 방식으로 보호됩니다. 프로그램 (커널 저장)에 의해 접촉 (참조 해제)되면 오류가 발생합니다.


1

가치가 있어야합니다. 분명히 사용자가 합법적으로 사용하고 싶어 할 수있는 가치를 밟고 싶지는 않습니다. C 런타임이 0으로 초기화 된 데이터에 대해 BSS 세그먼트를 제공하기 때문에 0을 초기화되지 않은 포인터 값으로 해석하는 것이 어느 정도 의미가 있다고 생각합니다.


0

OS가 주소 0에 쓰기를 허용하는 경우는 드뭅니다. OS 관련 항목을 낮은 메모리에 보관하는 것이 일반적입니다. 즉, IDT, 페이지 테이블 등입니다. (테이블은 RAM에 있어야하며 RAM의 상단이 어디에 있는지 확인하는 것보다 하단에 붙이는 것이 더 쉽습니다.) 그리고 올바른 생각의 어떤 OS도 당신을 허용하지 않을 것입니다. 시스템 테이블을 어리석게 편집하십시오.

이것은 K & R이 C를 만들었을 때 생각하지 않았을 지 모르지만 (0 == null은 기억하기 매우 쉽다는 사실과 함께) 0을 인기있는 선택으로 만듭니다.


이것은 보호 모드에서는 사실이 아니며 실제로 특정 Linux 구성에서는 가상 주소 0에 쓸 수 있습니다.
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳

0

0은 특정 표현에서 다양한 의미를 갖는 특별한 값입니다. 포인터의 경우 여러 번 지적했듯이 "기본 센티넬 값을 여기에 삽입"이라고 말하는 가장 편리한 방법 이었기 때문에 아마도 사용되었을 것입니다. 상수 표현식으로서 포인터 표현식의 컨텍스트에서 비트 0 (즉, 모든 비트가 0으로 설정 됨)과 동일한 의미를 갖지 않습니다. C ++에는 NULL포인터 멤버 및 멤버 함수에 대한 포인터와 같이 비트 0으로 표현되지 않는 여러 유형이 있습니다.

고맙게도 C ++ 0x에는 "정분 표현식에 대해 비트 0으로 매핑되지 않는 알려진 잘못된 포인터를 의미하는 표현식"에 대한 새 키워드가 nullptr있습니다.. Barfing없이 주소 0의 역 참조를 허용하는 C ++로 대상으로 지정할 수있는 시스템이 몇 가지 있지만 프로그래머는주의해야합니다.


0

이 스레드에는 이미 많은 좋은 답변이 있습니다. 0널 포인터 의 값을 선호하는 이유는 여러 가지가있을 수 있지만 두 가지를 더 추가하겠습니다.

  • C ++에서 포인터를 0으로 초기화하면 포인터가 null로 설정됩니다.
  • 많은 프로세서에서 값을 0으로 설정하거나 다른 상수보다 0과 같거나 같지 않은지 테스트하는 것이 더 효율적입니다.

0

이것은 C / C ++의 포인터 구현에 따라 다릅니다. 포인터 할당에서 NULL이 동일한 이유는 없습니다.


-1

이에 대한 역사적인 이유가 있지만 최적화 이유도 있습니다.

OS는 메모리 페이지가 0으로 초기화 된 프로세스를 제공하는 것이 일반적입니다. 프로그램이 해당 메모리 페이지의 일부를 포인터로 해석하려는 경우에는 0이므로 프로그램이 해당 포인터가 초기화되지 않았습니다. (초기화되지 않은 플래시 페이지에 적용될 때는 잘 작동하지 않습니다)

또 다른 이유는 많은 프로세서에서 0과 같은 값을 테스트하는 것이 매우 쉽다는 것입니다. 때로는 추가 명령없이 수행되는 무료 비교이며 일반적으로 다른 레지스터 또는 0 값을 제공 할 필요없이 수행 할 수 있습니다. 비교할 명령어 스트림의 리터럴로.

대부분의 프로세서에 대한 저렴한 비교는 부호가 0보다 작고 0과 같습니다. (부호가 0보다 크고 0이 아니라는 것은 둘 다에 의해 암시됩니다)

가능한 모든 값 중 하나의 값을 불량 또는 초기화되지 않은 것으로 예약해야하므로 불량 값과의 동등성에 대해 가장 저렴한 테스트를 수행하는 것이 좋습니다. '\ 0'으로 끝나는 문자열에 대해서도 마찬가지입니다.

이 목적을 위해 0보다 크거나 작게 사용하려고하면 주소 범위를 절반으로 자르게됩니다.


-2

상수는 0대신에 사용되는 NULLC 년 전에 어떤 원시인 수조에 의해 만들어진 때문, NULL, NIL, ZIP, 또는 NADDA이상의 모든 만든 훨씬 더 의미가있을 것입니다 0.

그러나 메모리 주소 지정이 0에서 시작하기 때문에 0이 다른 주소처럼 유효한 주소가 아닙니까?

과연. 많은 운영 체제가 가상 주소 공간에서도 0 번 주소에서 매핑하는 것을 허용하지 않지만 (사람들은 C가 안전하지 않은 언어라는 사실을 깨닫고 널 포인터 역 참조 버그가 매우 일반적이라는 것을 반영하여 다음을 허용하지 않음으로써 "수정"하기로 결정했습니다. 사용자 공간 코드를 페이지 0에 매핑합니다. 따라서 콜백을 호출했지만 콜백 포인터가 NULL이면 임의 코드를 실행하지 않습니다.

그렇다면 null 포인터를 처리하는 데 0을 어떻게 사용할 수 있습니까?

0포인터와 비교하여 사용 되기 때문에 malloc 실패시 malloc의 반환 값인 구현 특정 값 으로 대체됩니다 .

대신 음수가 null이 아닌 이유는 무엇입니까?

이것은 훨씬 더 혼란 스러울 것입니다.


"동굴"등에 대한 당신의 요점은 아마도 그것의 근원에있을 것입니다. 비록 세부 사항은 다르다고 생각합니다. C로 진화 한 초기 형태는 inta가 포인터와 크기가 같을뿐만 아니라 많은 컨텍스트 int에서 포인터와 상호 교환 적으로 사용될 수있는 특정 아키텍처에서 실행되도록 설계되었습니다 . 루틴이 포인터를 예상하고 하나는 정수 57로 전달되면 루틴은 숫자 57과 동일한 비트 패턴의 주소를 사용합니다. 이러한 특정 시스템에서 널 포인터를 나타내는 비트 패턴은 0이므로 int 0을 전달합니다. 널 포인터를 전달합니다.
supercat

그 이후로 C는 숫자와 포인터의 표현이 다른 매우 다양한 다른 기계를위한 프로그램을 작성하는 데 사용될 수 있도록 진화했습니다. 0이 아닌 숫자 상수는 포인터로 거의 사용되지 않았지만 상수 숫자 0은 널 포인터를 나타내는 데 널리 사용되었습니다. 이러한 사용을 허용하지 않으려면 기존 코드가 손상되었을 수 있으므로 컴파일러는 숫자 0을 구현에서 널 포인터를 나타내는 데 사용하는 모든 것으로 변환해야했습니다.
supercat 2013-09-23

-4

( 게시물을 읽기 전에이 단락을 읽으십시오.이 게시물을 읽는 데 관심이있는 사람은이 게시물을주의 깊게 읽어야하며, 완전히 이해할 때까지 투표하지 마십시오. 감사합니다. )

이제 커뮤니티 위키이므로 누군가 개념에 동의하지 않는 경우 무엇이 잘못되었고 왜 잘못되었는지에 대한 명확하고 자세한 설명과 함께 수정하십시오. 가능하면 출처를 인용하거나 복제 할 수있는 증거를 제공하십시오.

대답

NULL == 0의 기본 요인이 될 수있는 몇 가지 다른 이유가 있습니다.

  1. 0은 거짓이므로 if(!my_ptr)대신 직접 할 수 있습니다 if(my_ptr==NULL).
  2. 시작되지 않은 전역 정수는 기본적으로 모두 0으로 초기화되며 모든 0의 포인터는 초기화되지 않은 것으로 간주됩니다.

여기에 다른 답변에 대해 한 마디하고 싶습니다.

통사론 적 설탕 때문이 아니다

구문 설탕 때문에 NULL이 0이라고 말하는 것은 너무 의미가 없습니다. 그렇다면 배열의 인덱스 0을 사용하여 길이를 유지하지 않는 이유는 무엇입니까?

실제로 C는 내부 구현과 가장 유사한 언어입니다. C가 구문 설탕 때문에 0을 선택했다고 말하는 것이 합리적입니까? 그들은 0을 NULL로 매핑하는 대신 (다른 많은 언어와 마찬가지로) 키워드 null을 제공합니다!

따라서 오늘부터는 구문 설탕 일 수 있지만 C 언어 개발자의 원래 의도는 구문 설탕이 아니라는 것이 분명합니다.

1) 사양

그러나 C 사양이 상수 0에서 널 포인터 (섹션 6.3.2.3)로 말하고 구현 정의로 NULL을 정의하는 것은 사실이지만 (C11 사양의 섹션 7.19, C99 사양의 7.17) C의 발명가가 쓴 책 "The C Programming Language"에는 다음이 섹션 5.4에 명시되어 있습니다.

C는 0이 데이터에 대해 유효한 주소가 아님을 보장하므로 0의 반환 값을 사용하여 비정상 이벤트 (이 경우 공백 없음)를 알릴 수 있습니다.

포인터와 정수는 상호 교환 할 수 없습니다. 0은 유일한 예외입니다. 상수 0은 포인터에 할당 될 수 있으며 포인터는 상수 0과 비교할 수 있습니다. 기호 상수 NULL은 종종 포인터에 대한 특수 값임을 더 명확하게 나타내는 니모닉으로 0 대신 사용됩니다. NULL은에 정의되어 있습니다. 앞으로는 NULL을 사용합니다.

사람이 볼 수 있듯이 ( "제로 주소"라는 단어에서) 적어도 C 작성자의 원래 의도는 주소 0이었고 상수 0이 아니 었습니다. 또한이 발췌문에서 사양이 말하는 이유는 상수 0은 아마도 0으로 평가되는 표현식을 제외하지 않고 대신에 캐스트없이 포인터 컨텍스트에서 사용할 수있는 유일한 정수 상수 인 정수 상수 0을 포함하는 것입니다.

2) 요약

사양은 제로 주소가 제로 상수와 다르게 처리 될 수 있다고 명시 적으로 말하지는 않지만, 그렇지 않다고 말하지 않으며, 널 포인터 상수를 다룰 때 그것이 정의 된 구현이라고 주장하지 않습니다. NULL로 정의 된 상수에 의해 수행되고 대신 0이라고 주장하면 0 상수와 0 주소 사이에 차이가있을 수 있음을 보여줍니다.

(그러나 이것이 그렇다면 컴파일러가 어쨌든 모든 0 상수를 NULL로 정의 된 실제 구현으로 변환해야하기 때문에 NULL이 구현이 정의 된 이유가 궁금합니다.)

그러나 실제로는 이것을 보지 못하며 일반적인 플랫폼에서는 주소 0과 상수 0이 동일하게 취급되고 동일한 오류 메시지가 발생합니다.

게다가 오늘날의 운영 체제는 C의 NULL 포인터로 인해 제로 주소에 대한 액세스를 방지하기 위해 실제로 전체 첫 페이지 (범위 0x0000 ~ 0xFFFF)를 예약하고 있습니다 ( http://en.wikipedia.org/wiki/ 참조) . Zero_page 및 "Jeffrey Richter 및 Christophe Nasarre의 Windows Via C / C ++ (Microsoft Press에서 게시)").

따라서 실제로 실제로 본 적이 있다고 주장하는 사람에게 플랫폼과 컴파일러, 그가 실제로 수행 한 정확한 코드를 지정해달라고 요청합니다 (하지만 사양의 모호한 정의로 인해 모든 컴파일러). 플랫폼은 그가 원하는 것을 자유롭게 할 수 있습니다.)

그러나 C의 저자는 이것을 염두에 두지 않은 것 같습니다. 그리고 그들은 "0 주소"에 대해 말하고 있었고 "C는 그것이 유효한 주소가 아님을 보장합니다"뿐만 아니라 "NULL은 단지 mnemonic ", 원래 의도가"구문 설탕 "이 아니라는 것을 분명히 보여줍니다.

운영 체제 때문이 아닙니다.

또한 운영 체제가 다음과 같은 몇 가지 이유로 주소 0에 대한 액세스를 거부한다고 주장합니다.

1) C가 쓰여졌을 때이 위키 페이지 http://en.wikipedia.org/wiki/Zero_page 에서 볼 수 있듯이 그러한 제한은 없었습니다 .

2) 사실 C 컴파일러는 메모리 주소 0에 액세스했습니다.

이것은 BellLabs ( http://www.cs.bell-labs.com/who/dmr/primevalC.html ) 의 다음 논문에서 나온 사실 인 것으로 보입니다 .

두 컴파일러는 이에 대처하는 방법에있어 세부 사항이 다릅니다. 이전 버전에서는 함수 이름을 지정하여 시작을 찾습니다. 나중에 시작은 단순히 0으로 간주됩니다. 이것은 첫 번째 컴파일러가 메모리 매핑이있는 시스템을 갖기 전에 작성되었음을 나타냅니다. 따라서 프로그램의 원점은 위치 0에 없었던 반면 두 번째 시점에는 매핑을 제공하는 PDP-11이있었습니다.

(사실 오늘부터 (위키피디아와 마이크로 소프트 프레스에서 위의 참고 문헌을 인용했듯이) 제로 주소에 대한 액세스를 제한하는 이유는 C의 NULL 포인터 때문입니다! 결국에는 반대 방향으로 밝혀졌습니다!)

3) C는 운영 체제를 작성하는데도 사용되며 C 컴파일러도 사용합니다!

사실 C는 UNIX 운영 체제를 작성할 목적으로 개발되었으므로 주소 0에서 스스로를 제한해야하는 이유가 아닌 것 같습니다.

(하드웨어) 컴퓨터가 (물리적으로) 주소 0에 액세스 할 수있는 방법에 대한 설명

여기서 설명하고 싶은 또 다른 점이 있습니다. 주소 0을 참조 할 수있는 방법은 무엇입니까?

잠시 생각해 보면, 주소는 프로세서에 의해 페치 된 다음 메모리 버스의 전압으로 전송되고 메모리 시스템에서 실제 주소에 도달하는 데 사용되지만 주소가 0이면 전압이 없음을 의미합니다. , 메모리 시스템의 물리적 하드웨어가 주소 0에 어떻게 액세스합니까?

대답은 그 주소 0이 기본값이며, 즉 메모리 버스가 완전히 꺼져있을 때 메모리 시스템에서 항상 주소 0에 액세스 할 수 있으며 실제 주소를 지정하지 않고 읽기 또는 쓰기 요청을하는 것입니다. 주소가 0 인 경우) 자동으로 주소 0에 액세스합니다.


1
나는 당신을 반대 투표하지 않았지만 당신의 게시물에는 몇 가지 사실적인 부정확성이 있습니다. 오프셋 0의 물리적 메모리는 액세스 할 수 없습니다 (모든 스위치가 꺼져 있습니까? 정말?), 0 및 상수 0은 상호 교환 가능 (그렇지 않을 수도 있음) 및 기타.
Hasturkun 2013

0과 상수 0과 관련하여 이것은 원래 책이 말하는 것이고 이것이 실제 테스트에서 보여준 것입니다. 둘 사이에 실제 차이를 발견 했습니까? 그렇다면 어떤 컴파일러와 플랫폼입니까? 많은 답변이 내가 발견하지 못한 차이점이 있음을 시사하지만 차이점을 보여줄 참조가 없습니다. 사실 en.wikipedia.org/wiki/Zero_page 에 따르면 "Windows Via C / C ++ by Jeffrey Richter 및 Christophe Nasarre (Microsoft Press에서 게시)"전체 첫 페이지! Null을 방지하기 위해 최신 컴퓨터에서 보호됩니다 (실제로는 1 바이트 이상 낭비!)
yoel halb

물론 주소의 비트 패턴은 읽고있는 것을 선택하는 데 사용됩니다. 일반적으로 그렇습니다. 어쨌든, 나는 당신과 논쟁하고 싶지 않습니다. 왜 당신이 반대표를 받았을지도 지적했습니다.
Hasturkun 2013

나는 당신의 주장에 동의하지 않습니다. 나는 또한이 토론을 계속하는 데 관심이 없습니다.
Hasturkun 2013

6
하드웨어 주장은 말도 안됩니다. 주소 0을 읽으려면! Chip Select low,! RAS high,! CAS low,! WE high, 모든 주소 라인을 low로 구동합니다. 버스가 꺼져 있으면! CS가 높습니다.
MSalters jul.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.