C 또는 C ++에서 "널 검사"를 수행한다는 것은 무엇을 의미합니까?


21

나는 C ++을 배우고 있으며 null을 이해하는 데 어려움을 겪고 있습니다. 특히, 내가 읽은 자습서는 "널 체크"를 수행한다고 언급했지만 그것이 무엇을 의미하는지 또는 왜 필요한지 잘 모르겠습니다.

  • null이 정확히 무엇입니까?
  • "널 확인"은 무엇을 의미합니까?
  • 항상 null을 확인해야합니까?

모든 코드 예제는 대단히 감사하겠습니다.



당신이 읽은 모든 것들이 널 점검에 대해 설명하고 예제 코드를 제공하지 않고 null check에 대해 이야기한다면 ... 더 나은 튜토리얼을 얻는 것이 좋습니다 ...
underscore_d

답변:


26

C 및 C ++에서 포인터는 본질적으로 안전하지 않습니다. 즉, 포인터를 역 참조 할 때는 포인터가 유효한 위치를 가리키는 것이 본인의 책임입니다. 이것은 "수동 메모리 관리"의 일부입니다 (Java, PHP 또는 .NET 런타임과 같은 언어로 구현 된 자동 메모리 관리 체계와는 달리 상당한 노력 없이는 유효하지 않은 참조를 작성할 수 없습니다).

많은 오류를 포착하는 일반적인 솔루션은 포인터를 액세스하기 전에 NULL(또는 올바른 C ++에서 0) 아무것도 가리 키지 않는 모든 포인터를 설정하고 포인터를 확인하는 것입니다. 당신이 경우 특히, (당신은 이미 당신이 그들을 선언 할 때 그들을 가리 키도록 뭔가를하지 않는 한) NULL로 모든 포인터를 초기화하고 NULL로 설정하는 것이 일반적이다 delete또는 free()그들 (그들은 즉시 그 후 범위 밖으로 이동하지 않는 한). 예 (C에서는 유효하지만 C ++에서도) :

void fill_foo(int* foo) {
    *foo = 23; // this will crash and burn if foo is NULL
}

더 나은 버전 :

void fill_foo(int* foo) {
    if (!foo) { // this is the NULL check
        printf("This is wrong\n");
        return;
    }
    *foo = 23;
}

널 검사가 없으면 NULL 포인터를이 함수에 전달하면 segfault가 발생하고 수행 할 수있는 작업이 없습니다. OS는 단순히 프로세스를 종료하고 코어 덤프 또는 충돌 보고서 대화 상자를 표시합니다. 널 점검을 사용하면 적절한 오류 처리를 수행하고 정상적으로 복구 할 수 있습니다. 문제를 직접 수정하고, 현재 작업을 중단하고, 로그 항목을 작성하고, 사용자에게 알맞게 알리십시오.


3
@MrLister C ++에서 null 검사가 작동하지 않는다는 것은 무엇을 의미합니까? 선언 할 때 포인터를 null로 초기화하면됩니다.
TZHX

1
내 말은 포인터를 NULL로 설정해야 작동하지 않습니다. 즉 , 포인터가 NULL임을 알고 있다면 어쨌든 fill_foo를 호출 할 필요가 없습니다. fill_foo는 포인터에 유효한 값 이 아닌지 포인터에 값이 있는지 확인 합니다. C ++에서 포인터는 유효한 값을 갖는 NULL을 보증하지 않습니다.
Mr Lister

4
assert ()가 더 나은 솔루션입니다. "안전하다"는 것은 의미가 없습니다. NULL이 전달되면 분명히 잘못되었으므로 프로그래머가 완전히 인식하도록 명시 적으로 충돌하지 않는 이유는 무엇입니까? (당신이 한 때문에 생산에, 그것이 중요하지 않습니다 입증 ?. 아무도 fill_foo () NULL로 호출하지 것, 바로 정말, 그 어렵지 않다)
Ambroz Bizjak

7
이 함수의 더 나은 버전은 포인터 대신 참조를 사용하여 NULL 검사를 더 이상 사용하지 않아야한다는 점을 잊지 마십시오.
Doc Brown

4
이것은 수동 메모리 관리에 관한 것이 아니며, null 참조를 역 참조하려고하면 관리되는 프로그램도 폭발합니다 (또는 대부분의 언어에서 기본 프로그램처럼 예외를 발생시킵니다).
메이슨 휠러

7

다른 답변은 귀하의 정확한 질문에 거의 적용되었습니다. 수신 한 포인터가 실제로 유형의 유효한 인스턴스 (객체, 기본 요소 등)를 가리키는 지 확인하기 위해 널 검사가 수행됩니다.

그래도 여기에 나만의 조언을 추가 할 것입니다. 널 검사를 피하십시오. :) 널 검사 (및 다른 형태의 방어 프로그래밍)는 코드를 어지럽히고 실제로 다른 오류 처리 기술보다 오류가 발생하기 쉽습니다.

객체 포인터와 관련하여 내가 가장 좋아하는 기술은 Null Object 패턴 을 사용하는 것 입니다 . 즉, null 대신 빈 배열 또는 목록을 가리키는 (포인터 또는 더 나은 참조) 또는 null 대신 빈 문자열 ( "") 또는 문자열 "0"(또는 "nothing" 문맥에서 ")를 사용하여 정수로 파싱 할 것으로 예상합니다.

보너스로, 1965 년 CAR Hoare가 Algol W 언어를 위해 (공식적으로) 구현 한 널 포인터에 대해 알지 못했을 것입니다.

나는 그것을 10 억 달러의 실수라고 부릅니다. 그것은 1965 년에 null 참조의 발명이었다. 당시 나는 객체 지향 언어 (ALGOL W)로 참조 할 수있는 최초의 포괄적 인 타입 시스템을 설계하고 있었다. 필자의 목표는 컴파일러가 자동으로 검사를 수행하여 모든 참조 사용이 절대적으로 안전하도록하는 것이 었습니다. 그러나 구현하기가 쉽기 때문에 null 참조를 넣는 유혹에 저항 할 수 없었습니다. 이로 인해 수많은 오류, 취약성 및 시스템 충돌이 발생하여 지난 40 년간 수십억 달러의 고통과 피해를 초래했을 수 있습니다.


6
널 오브젝트는 널 포인터를 갖는 것보다 훨씬 나쁩니다. 알고리즘 X가 가지고 있지 않은 데이터 Y를 요구한다면 , 그것은 프로그램버그입니다 .
DeadMG

그것은 상황에 달려 있으며, "데이터 존재"에 대한 테스트는 제 책에서 null 테스트보다 빠릅니다. 내 경험에 따르면, 알고리즘이 목록에서 작동하고 목록이 비어 있으면 알고리즘은 아무런 관련이 없으며 for / foreach와 같은 표준 제어문을 사용하여 수행합니다.
Yam Marcovic

알고리즘이 수행 할 작업이 없으면 왜 호출하는 것입니까? 그리고 당신이 처음에 그것을 부르고 싶었던 이유는 그것이 중요한 일을하기 때문 입니다.
DeadMG

@DeadMG 프로그램은 입력에 관한 것이며 실제 환경에서는 숙제와 달리 입력이 관련이 없을 수 있습니다 (예 : 비어 있음). 코드는 여전히 어느 쪽이든 호출됩니다. 두 가지 옵션이 있습니다. 관련성 (또는 공허함)을 확인하거나 조건문을 사용하여 관련성을 명시 적으로 확인하지 않고 알고리즘을 읽고 잘 작동하도록 알고리즘을 설계합니다.
얌 마르코비치

나는 거의 같은 의견을 제시하기 위해 여기에 왔으므로 대신 당신에게 내 투표를주었습니다. 그러나 나는 이것이 좀비 객체 의 더 큰 문제를 대표한다고 덧붙일 것입니다 -다단계 초기화 (또는 파괴)가 완전히 존재하지 않지만 완전히 죽지 않은 객체를 가질 때마다. 결정적인 종결이없는 언어로 "안전한"코드를 볼 때, 모든 기능에 점검을 추가하여 객체가 폐기되었는지 확인하는 것이 일반적인 문제입니다. 널 (null)이면 절대 사용하지 않아야하며, 수명 동안 필요한 오브젝트가있는 상태로 작업해야합니다.
ex0du5 2016 년

4

널 포인터 값은 잘 정의 된 "nowhere"를 나타냅니다. 다른 포인터 값과 같지 않은 것을 비교할 수 있는 유효하지 않은 포인터 값입니다. 널 포인터의 역 참조를 시도하면 정의되지 않은 동작이 발생하고 일반적으로 런타임 오류가 발생하므로 역 참조를 시도하기 전에 포인터가 NULL이 아닌지 확인하려고합니다. 많은 C 및 C ++ 라이브러리 함수는 오류 조건을 표시하기 위해 널 포인터를 리턴합니다. 예를 들어, 라이브러리 함수 malloc는 요청 된 바이트 수를 할당 할 수없는 경우 널 포인터 값을 리턴하고 해당 포인터를 통해 메모리에 액세스하려고하면 (일반적으로) 런타임 오류가 발생합니다.

int *p = malloc(sizeof *p * N);
p[0] = ...; // this will (usually) blow up if malloc returned NULL

따라서 NULL malloc값을 확인하여 호출이 성공 했는지 확인해야합니다 p.

int *p = malloc(sizeof *p * N);
if (p != NULL) // or just if (p)
  p[0] = ...;

자, 양말을 잠시만 기다려주십시오. 약간 울퉁불퉁합니다.

널 포인터 과 널 포인터 상수 가 있으며 둘이 반드시 같을 필요는 없습니다. 널 포인터 은 기본 아키텍처가 "nowhere"를 나타내는 데 사용하는 값입니다. 이 값은 0x00000000 또는 0xFFFFFFFF 또는 0xDEADBEEF이거나 완전히 다른 값일 수 있습니다. 널 포인터 이 항상 0 이라고 가정하지 마십시오 .

널 포인터 상수 OTOH는 항상 0 값의 정수 표현식입니다. 지금까지 당신과 같이 소스 코드를 우려, 0 (또는 통합 식 0 평가는 것을) 널 포인터를 나타냅니다. C와 C ++ 모두 NULL 매크로를 널 포인터 상수로 정의합니다. 코드가 컴파일되면 널 포인터 상수 가 생성 된 기계 코드에서 적절한 널 포인터 으로 대체됩니다 .

또한 NULL은 유효하지 않은 많은 포인터 값 중 하나 일뿐입니다 . 명시 적으로 초기화하지 않고 자동 포인터 변수를 선언하는 경우

int *p;

변수에 처음 저장된 값은 미정 이며 유효하거나 액세스 가능한 메모리 주소에 해당하지 않을 수 있습니다. 불행히도, 널 (NULL)이 아닌 포인터 값을 사용하기 전에 유효한지 여부를 알 수있는 (이동식) 방법은 없습니다. 따라서 포인터를 다루는 경우 일반적으로 선언 할 때 명시 적으로 NULL로 초기화하고 적극적으로 무언가를 가리 키지 않을 때 NULL로 설정하는 것이 좋습니다.

이것은 C ++보다 C에서 더 큰 문제입니다. 관용적 C ++은 포인터를 많이 사용해서는 안됩니다.


3

몇 가지 방법이 있으며 모두 본질적으로 동일한 일을합니다.

int * foo = NULL; // 때때로 NULL 대신 0x00 또는 0 또는 0L로 설정

널 확인 (포인터가 널인지 확인), 버전 A

if (foo == NULL)

널 확인, 버전 B

if (! foo) // NULL이 0으로 정의되었으므로! foo는 널 포인터에서 값을 리턴합니다.

null 확인, 버전 C

if (foo == 0)

세 가지 중에서 첫 번째 검사는 미래의 개발자에게 무엇을 확인하려고하는지 명시 적으로 알려주고 foo가 포인터가 될 것으로 예상한다는 것을 명확하게 알려주는 것을 선호합니다.


2

당신은하지 않습니다. C ++에서 포인터를 사용하는 유일한 이유는 널 포인터의 존재를 명시 적으로 원하기 때문입니다. 그렇지 않으면 의미 적으로 사용하기 쉽고 null이 아닌 것을 보장하는 참조를 사용할 수 있습니다.


1
@ 제임스 : 커널 모드에서 '신규'?
Nemanja Trifunovic 2016 년

1
@James : 상당수의 C ++ 코더가 즐기는 기능을 나타내는 C ++ 구현. 여기에는 모든 C ++ 03 언어 기능 (제외 export)과 모든 C ++ 03 라이브러리 기능 TR1 C ++ 11의 좋은 덩어리가 포함됩니다.
DeadMG

5
내가 소원 사람들이 있음을 언급하지 않았다 "참조가 null을 보장합니다." 그들은하지 않습니다. 널 포인터처럼 널 참조를 생성하는 것이 쉽고 동일한 방식으로 전파됩니다.
mjfgates (1

2
@Stargazer : 언어 디자이너와 모범 사례에서 제안하는 방식으로 도구를 사용하면 문제가 100 % 중복됩니다.
DeadMG

2
@DeadMG, 중복 여부는 중요하지 않습니다. 당신 은 질문에 대답하지 않았습니다 . 다시 말하겠습니다 : -1.
riwalk

-1

NULL 값을 확인하지 않으면, 특히 구조체에 대한 포인터 인 경우 보안 취약점-NULL 포인터 역 참조가 발생했을 수 있습니다. NULL 포인터 역 참조는 버퍼 오버플로, 경쟁 조건 등과 같이 공격자가 컴퓨터를 제어 할 수있는 다른 심각한 보안 취약점으로 이어질 수 있습니다.

Microsoft, Oracle, Adobe, Apple과 같은 많은 소프트웨어 공급 업체는 이러한 보안 취약점을 해결하기 위해 소프트웨어 패치를 출시합니다. 각 포인터의 NULL 값을 확인해야한다고 생각합니다. :)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.