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

C ++를 배우고 있는데 null을 이해하는 데 어려움을 겪고 있습니다. 특히 내가 읽은 자습서에서 “null 검사”를 수행한다고 언급했지만 그게 무슨 뜻인지, 왜 필요한지 잘 모르겠습니다.

  • 정확히 null이 무엇인가요?
  • “null 확인”이란 무엇을 의미합니까?
  • 항상 null을 확인해야합니까?

어떤 코드 예제라도 감사하겠습니다.

댓글

Answer

C 및 C ++에서 포인터는 본질적으로 안전하지 않습니다. 즉, 포인터를 역 참조 할 때 유효한 어딘가를 가리키는 것은 여러분 자신의 책임입니다. 이것은 “수동 메모리 관리”의 일부입니다 (Java와 같은 언어로 구현 된 자동 메모리 관리 체계와 반대) , PHP 또는 .NET 런타임. “많은 노력 없이는 유효하지 않은 참조를 생성 할 수 없습니다.)

많은 오류를 포착하는 일반적인 솔루션은 아무것도 가리 키지 않는 모든 포인터를 설정하는 것입니다. NULL (또는 올바른 C ++, 0)로 지정하고 포인터에 액세스하기 전에 확인합니다. 특히, 모든 포인터를 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 검사가 없으면이 함수에 NULL 포인터를 전달하면 segfault가 발생하며 사용자가 할 수있는 작업이 없습니다. OS는 단순히 프로세스를 종료하고 코어 덤프 또는 충돌 보고서 대화 상자를 표시합니다. null 체크를 사용하면 적절한 오류 처리를 수행하고 정상적으로 복구 할 수 있습니다. 문제를 직접 수정하고, 현재 작업을 중단하고, 로그 항목을 작성하고, 사용자에게 적절한 내용을 알립니다.

Comments

Comments

  • @MrLister 무슨 뜻입니까? null 검사는 C ++에서 작동하지 않습니다 ‘? 선언 할 때 포인터를 null로 초기화하면됩니다.
  • 내 말은 포인터를 NULL로 설정하는 것을 기억해야합니다. 그렇지 않으면 이겼습니다. ‘ t 작동합니다. 즉, 포인터가 NULL이라는 것을 알고 기억한다면 ‘ 어쨌든 fill_foo를 호출 할 필요가 없습니다. fill_foo는 포인터에 유효한 값이 있는지가 아니라 포인터에 값이 있는지 확인합니다. C ++에서 포인터는 유효한 값을 가지고있는 NULL도 보장되지 않습니다.
  • assert ()가 여기서 더 나은 해결책이 될 것입니다. ‘ ” 안전하게 ” 노력할 필요가 없습니다. NULL이 전달 된 경우 ‘ 분명히 잘못된 것입니다. 프로그래머가 완전히 인식하도록 명시 적으로 충돌하지 않는 이유는 무엇입니까? (그리고 프로덕션에서 ‘ 중요하지 않습니다. ‘ 아무도 fill_foo를 호출하지 않는다는 것을 증명 했기 때문입니다. () NULL이있는 거죠? 정말 ‘별로 어렵지 않습니다.)
  • ‘ 잊지 마세요. 이 함수의 더 나은 버전은 포인터 대신 참조를 사용해야하므로 NULL 검사를 쓸모 없게 만듭니다.
  • 이것은 수동 메모리 관리에 관한 것이 아니며 관리 프로그램도 폭발합니다. 또는 최소한 대부분의 언어에서 네이티브 프로그램처럼 예외를 발생시킵니다.) null 참조를 역 참조하려고하면

Answer

다른 답변은 귀하의 정확한 질문을 거의 다룹니다. 수신 한 포인터가 실제로 유효한 유형의 인스턴스 (객체, 프리미티브 등)를 가리키는 지 확인하기 위해 null 검사가 수행됩니다.

여기에 제 조언을 추가하겠습니다. null 검사는 피하십시오. 🙂 Null 검사 (및 다른 형태의 방어 프로그래밍)는 코드를 복잡하게 만들고 실제로 다른 오류 처리 기술보다 오류가 발생하기 쉽습니다.

내가 가장 좋아하는 기술은 개체 포인터는 Null 개체 패턴 을 사용하는 것입니다. 즉, null 대신 빈 배열 또는 목록 (포인터 또는 더 나은 참조)을 반환하거나 null 대신 빈 문자열 ( “”)을 반환하거나 정수로 구문 분석 될 것으로 예상되는 문자열 “0”(또는 컨텍스트에서 “nothing”에 해당하는 것)을 반환합니다.

보너스로 1965 년에 CAR Hoare가 (처음으로 공식적으로) 구현 한 널 포인터에 대해 알지 못했던 약간의 정보가 있습니다.

저는 이것을 제 10 억 달러의 실수라고 부릅니다. 이것은 1965 년에 null 참조의 발명이었습니다. 그 당시 저는 객체에서 참조를위한 최초의 포괄적 인 유형 시스템을 설계하고있었습니다. 지향 언어 (ALGOL W). 내 목표는 모든 참조 사용이 절대적으로 안전해야하며 컴파일러에 의해 자동으로 수행되는 검사를 보장하는 것이 었습니다.하지만 단순히 그렇게했기 때문에 null 참조를 입력하려는 유혹에 저항 할 수 없었습니다. 구현하기 쉽습니다. 이로 인해 수많은 오류, 취약성 및 시스템 충돌이 발생하여 지난 40 년 동안 수십억 달러의 고통과 피해를 입혔을 것입니다.

댓글

  • Null 개체는 단순히 null 포인터를 갖는 것보다 더 나쁩니다. 알고리즘 X가 가지고 있지 않은 데이터 Y를 필요로한다면, 그것은 프로그램의 버그 이며, 당신이 그렇게하는 척하여 숨기는 것입니다.
  • 컨텍스트 및 ” 데이터 존재 여부 ” 테스트가 내 책에서 null 테스트를 능가합니다. 내 경험상 알고리즘이 목록에서 작동하고 목록이 비어 있으면 알고리즘은 할 일이 없으며 for / foreach와 같은 표준 제어문을 사용하여 수행합니다.
  • 알고리즘이 할 일이 없는데 왜 호출하는 거죠? 그리고 처음에 그것을 부르고 싶었던 이유는 중요한 일을하기 때문입니다 입니다.
  • @DeadMG 프로그램은 입력에 관한 것이기 때문입니다. 숙제, 입력은 관련이 없을 수 있습니다 (예 : 비어 있음). 코드는 여전히 어느 쪽이든 호출됩니다. 두 가지 옵션이 있습니다. 관련성 (또는 비어 있음)을 확인하거나 조건문을 사용하여 명시 적으로 관련성을 확인하지 않고 잘 읽고 작동하도록 알고리즘을 설계합니다.
  • 저는 거의 같은 댓글로 대신 내 투표를주었습니다. 그러나 이것이 좀비 오브젝트 의 더 큰 문제를 대표한다고 덧붙이고 싶습니다. 다단계 초기화 (또는 파괴)가있는 오브젝트가 완전히 살아 있지 않지만 완전히 죽지 않은 오브젝트가있을 때마다 말입니다. 객체가 삭제되었는지 확인하기 위해 모든 함수에 검사를 추가 한 결정적 마무리가없는 언어로 ” 안전한 ” 코드가 표시되면, 머리를 키우는 것이 ‘ 일반적인 문제입니다. null 인 경우는 절대로 안되며 수명 동안 필요한 객체가있는 상태로 작업해야합니다.

답변

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

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

따라서 다음 값을 확인하여 malloc 호출이 성공했는지 확인해야합니다. NULL에 대한 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 ++는 포인터를 그다지 많이 사용해서는 안됩니다.

답변

두 가지 방법이 있으며 모두 기본적으로 동일합니다.

 int *foo = NULL; //sometimes set to 0x00 or 0 or 0L instead of NULL 

null 검사 (포인터가 null인지 확인), 버전 A

 if( foo == NULL) 

null 검사, 버전 B

 if( !foo ) //since NULL is defined as 0, !foo will return a value from a null pointer 

null 검사, 버전 C

 if( foo == 0 ) 

이 세 가지 중 첫 번째 검사를 사용하는 것을 선호합니다.이 검사는 미래의 개발자에게 검사하려는 내용을 명시 적으로 알려주고 foo가 포인터가 될 것으로 예상했음을 분명히합니다.

답변

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

댓글

  • @James : ‘ 새로운 ‘?
  • @James : C ++의 대부분이 차지하는 기능을 나타내는 C ++ 구현 코더는 즐깁니다. 여기에는 모든 C ++ 03 언어 기능 (export 제외)과 모든 C ++ 03 라이브러리 기능 TR1 그리고 좋은 C ++ 11 덩어리입니다.
  • 사람들이 ‘ ” 참조는 null이 아닌 것을 보장합니다. ” 참조는 ‘이 아닙니다. null 참조는 null 포인터처럼 쉽게 생성 할 수 있으며 동일한 방식으로 전파됩니다.
  • @Stargazer : 언어 디자이너와 좋은 방식으로 도구를 사용할 때 문제는 100 % 중복됩니다. 연습 할 것을 제안합니다.
  • @DeadMG, 중복 여부는 중요하지 않습니다. ‘ 질문에 답변하지 않았습니다 ‘. ‘ 다시 말하겠습니다 : -1.

답변

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

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

답글 남기기

이메일 주소를 발행하지 않을 것입니다. 필수 항목은 *(으)로 표시합니다