역 참조 된 모든 단일 포인터를 널 보호하는 것이 합리적입니까?


65

새로운 직업에서, 나는 다음과 같은 코드에 대한 코드 리뷰에 플래그를 붙였습니다.

PowerManager::PowerManager(IMsgSender* msgSender)
  : msgSender_(msgSender) { }

void PowerManager::SignalShutdown()
{
    msgSender_->sendMsg("shutdown()");
}

마지막 방법은 다음과 같아야한다고 들었습니다.

void PowerManager::SignalShutdown()
{
    if (msgSender_) {
        msgSender_->sendMsg("shutdown()");
    }
}

즉, 내가 해야한다NULL주위에 가드 msgSender_가 개인 데이터 멤버 인 경우에도 변수입니다. 내가이 '지혜'에 대해 어떻게 느끼는지 설명하기 위해 expletives를 사용하는 것을 자제하는 것은 어렵다. 설명을 요청하면 몇 년 동안 일부 주니어 프로그래머가 클래스가 어떻게 작동해야하는지에 대해 혼란스러워하고 자신이 없어야 할 멤버를 실수로 삭제 한 NULL후 (그리고 나중에 설정해야 함) 무서운 이야기를 얻습니다. 제품 출시 직후 현장에서 문제가 발생했으며 모든 것을NULL 확인하는 것이 더 낫다는 "어려운 방법을 배우고 우리를 믿습니다" .

나에게 이것은 화물 컬트 프로그래밍 처럼 느껴지고 단순하고 단순합니다. 선의의 동료 몇 명이 저에게 '얻기'를 돕고 이것이 더 강력한 코드를 작성하는 데 어떻게 도움이되는지 알아 보려고 노력하고 있습니다. 그러나 나는 그들이 그것을 얻지 못하는 사람이라는 느낌을 줄 수 없습니다. .

코딩 표준 에서 함수에서 역 참조 된 모든 단일 포인터가 NULL개인 데이터 멤버까지도 먼저 확인 하도록 요구하는 것이 합리적 입니까? (참고 : 상황을 설명하기 위해 항공 교통 관제 시스템이나 기타 '실패-사람-사람'제품이 아닌 가전 제품을 만듭니다.)

편집 : 위의 예에서 msgSender_공동 작업자는 선택 사항이 아닙니다. 항상 NULL이면 버그를 나타냅니다. 생성자로 전달되는 유일한 이유 PowerManager는 모의 IMsgSender서브 클래스 로 테스트 할 수 있기 때문 입니다.

요약 :이 질문에 정말 좋은 답변이있었습니다. 모두 감사합니다. 나는 @aaronps에서 주로 간결함으로 인해 하나를 수락했습니다. 다음과 같은 상당히 일반적인 계약이있는 것 같습니다.

  1. 의무화 NULL에 대한 경비를 역 참조 포인터 것은 과잉이지만,
  2. 참조 (가능한 경우) 또는 const포인터 를 사용하여 전체 토론을 회피 할 수 있습니다.
  3. assert명령문은 NULL기능의 전제 조건이 충족되는지 확인하기 위해 경비원에 대한 보다 계몽 된 대안 입니다.

14
실수는 주니어 프로그래머의 영역이라고 잠시 생각하지 마십시오. 모든 수준의 경험을 가진 개발자의 95 %가 한 번에 한 번 위태롭게합니다. 나머지 5 %는 이빨을 통해 누워 있습니다.
Blrfl

56
누군가가 null을 확인하고 자동으로 실패하는 코드를 작성하는 것을 보았을 때 즉시 그 자리에서 발사하고 싶습니다.
Winston Ewert

16
조용히 실패하는 것은 거의 최악입니다. 적어도 폭발 할 때 폭발이 발생하는 곳을 알고 있습니다. 점검 null하고 아무것도하지 않는 것은 버그를 실행 아래로 옮기는 방법 일 뿐이므로 소스로 다시 추적하기가 훨씬 어렵습니다.
MirroredFate

1
+1, 매우 흥미로운 질문입니다. 내 대답 외에도 단위 테스트를 수용하는 목적으로 코드를 작성해야 할 때 실제로하는 일은 잘못된 보안 감각에 대한 위험을 증가시키는 것입니다 (더 많은 코드 = 버그에 대한 더 많은 가능성). 참조를 사용하도록 다시 디자인하면 코드의 가독성이 향상 될뿐만 아니라 코드의 작동 가능성을 증명하기 위해 작성해야하는 코드 (및 테스트)의 양이 줄어 듭니다.
세스

6
@Rig, 나는 자동 실패에 대한 적절한 경우가 있다고 확신합니다. 그러나 누군가가 일반적인 관행으로 침묵하는 것을 실패한다면 (이해가되는 영역에서 작업하지 않는 한) 그들이 코드를 넣는 프로젝트에서 일하고 싶지는 않습니다.
Winston Ewert

답변:


66

'계약'에 따라 다릅니다.

PowerManager 반드시 유효한 경우 반드시IMsgSender null을 확인하지 말고 더 빨리 죽도록하십시오.

다른 한편으로,이 경우 수도 을 가지고 IMsgSender, 당신은 그 한 간단하게, 당신은 사용할 때마다 확인해야합니다.

후배 프로그래머의 이야기에 대한 최종 의견, 문제는 실제로 테스트 절차의 부족입니다.


4
내가 생각하는 방식을 요약하는 매우 간결한 대답에 +1 : "의존한다 ..."또한 "null check for null"( 널이 유효하지 않은 경우) 라고하는 용기도 있었다 . 배치 assert()포인터를 역 참조 모든 멤버 함수은 가능성이 많은 사람들을 회유하는 타협이지만, 심지어는 나에게 '투기 편집증'같은 느낌. 내가 통제 할 수있는 능력 이상의 것들의 목록은 무한하며, 일단 내가 걱정하기 시작하면 정말 미끄러운 경사가됩니다. 내 테스트, 동료 및 디버거를 신뢰하는 법을 배우면 밤에 더 잘 수 있습니다.
evadeflow

2
코드를 읽을 때 이러한 어설 션은 코드의 작동 방식을 이해하는 데 매우 도움이 될 수 있습니다. 나는 '이 모든 주장이 여기 저기 있지 않았 으면 좋겠다.'고 생각한 코드베이스를 권장하지는 않았지만 반대의 말을 많이 보았습니다.
Kristof Provost

1
간단하고 간단하며 질문에 대한 정답입니다.
Matthew Azkimov

2
이것은 가장 가까운 정답이지만 'null check for null'에 동의 할 수 없으며 항상 멤버 변수에 할당 될 시점에서 유효하지 않은 매개 변수를 확인하십시오.
James

의견 주셔서 감사합니다. 누군가가 사양을 읽지 않고 유효한 것이 있어야 할 때 null 값으로 초기화하면 더 빨리 충돌하는 것이 좋습니다.
aaronps

77

코드를 읽어야한다고 생각합니다.

PowerManager::PowerManager(IMsgSender* msgSender)
  : msgSender_(msgSender)
{
    assert(msgSender);
}

void PowerManager::SignalShutdown()
{
    assert(msgSender_);
    msgSender_->sendMsg("shutdown()");
}

이것은 NULL을 지키는 것보다 실제로 낫습니다. NULL 인 경우 함수를 호출해서는 안된다는 것이 매우 분명하기 때문 msgSender_입니다. 또한 이런 일이 발생하면 알 수 있습니다.

동료의 공유 된 "지혜"는이 오류를 자동으로 무시하고 예기치 않은 결과를 초래합니다.

일반적으로 버그는 원인에 더 가깝게 감지되면 수정하기가 더 쉽습니다. 이 예에서 제안 된 NULL 보호는 종료 메시지가 설정되지 않아 눈에 띄는 버그가 발생할 수 있습니다. SignalShutdown전체 응용 프로그램이 방금 사망했을 때보 다 함수를 역으로 처리하는 데 어려움을 겪을 수 SignalShutdown()있습니다.

약간 반 직관적이지만 문제가 발생하자마자 충돌하면 코드가 더 강력 해집니다. 실제로 문제를 발견 하고 매우 명백한 원인이있는 경향이 있기 때문 입니다.


10
assert 호출과 OP 코드 예제의 가장 큰 차이점은 assert가 디버그 빌드에서만 활성화된다는 것입니다 (#define NDEBUG) 제품이 고객의 손에 들어오고 디버그가 비활성화되고 코드 경로의 절대적인 조합이 떠나도록 실행됩니다 문제의 함수가 실행 되더라도 포인터가 널이면 어설 션은 보호 기능을 제공하지 않습니다.
atk

17
아마도 고객에게 실제로 보내기 전에 빌드를 테스트했을 것입니다. 그러나 이것은 가능한 위험입니다. 해결 방법은 간단합니다. 어설 션이 활성화 된 상태 (어쨌든 테스트 한 버전)와 함께 제공되거나 디버그 모드에서 어설 션하고 로그, 로그 + 역 추적, 종료 등 릴리스 모드에서 자신의 매크로로 대체하십시오.
Kristof Provost

7
@James-90 %의 경우 실패한 NULL 검사에서도 복구되지 않습니다. 그러나 이러한 경우 코드가 자동으로 실패하고 나중에 나중에 오류가 발생할 수 있습니다. 예를 들어 파일에 저장했다고 생각했지만 실제로 null 검사는 때로는 더 현명하지 않은 채로 두지 못합니다. 발생해서는 안되는 모든 상황에서 복구 할 수 없습니다. 이러한 상황이 발생하지 않는지 확인할 수는 있지만 NASA 위성 또는 원자력 발전 소용 코드를 작성하지 않으면 예산이 없다고 생각합니다. "무슨 일이 벌어 질지 모르겠습니다. 프로그램이이 상태에 있지 않다"고 말하는 방법이 필요합니다.
Maciej Piechotka

6
@ 제임스는 던지기에 동의하지 않습니다. 실제로 일어날 수있는 것처럼 보입니다. 주장은 불가능하다고 생각되는 것들을위한 것이다. 다시 말해 코드의 실제 버그에 대한 것입니다. 예상치 못한 상황이 발생하면 예외가 발생합니다. 처리하고 복구 할 수있는 것은 예외입니다. 복구 할 수없는 코드의 논리 오류이며 시도하지 않아야합니다.
Kristof Provost

2
@ 제임스 : 임베디드 시스템에 대한 경험에서 소프트웨어와 하드웨어는 장치를 완전히 사용할 수 없게 만드는 것이 매우 어려운 방식으로 설계되었습니다. 대부분의 경우 소프트웨어 실행이 중지되면 장치를 완전히 재설정하는 하드웨어 감시 장치가 있습니다.
Bart van Ingen Schenau

30

msgSender이 경우 할 수 없다 null, 당신은 놓아야 null만 생성자에서 확인. 이것은 클래스에 대한 다른 입력의 경우에도 마찬가지입니다- '모듈'에 들어가는 시점에서 무결성 검사를하십시오-클래스, 함수 등

내가 경험하는 규칙은 모듈 경계 (이 경우 클래스) 사이에서 무결성 검사를 수행하는 것입니다. 또한 클래스는 클래스 멤버의 수명의 무결성을 신속하게 정신적으로 확인할 수있을 정도로 작아야합니다. 따라서 부적절한 삭제 / 널 지정과 같은 실수를 방지 할 수 있습니다. 게시물의 코드에서 수행되는 null 검사는 유효하지 않은 사용이 실제로 포인터에 null을 할당한다고 가정하지만 항상 그런 것은 아닙니다. '유효하지 않은 사용'은 본질적으로 일반 코드에 대한 가정이 적용되지 않음을 의미하므로 모든 유형의 포인터 오류 (예 : 잘못된 삭제, 증분 등)를 포착 할 수는 없습니다.

또한 인수가 널이 될 수 없다고 확신하는 경우 클래스 사용에 따라 참조 사용을 고려하십시오. 그렇지 않으면, 원시 포인터 대신 std::unique_ptr또는 std::shared_ptr원시 포인터를 사용하십시오.


11

아니요, 포인터가 가리키는 모든 포인터 역 참조 를 확인하는 것은 합리적이지 않습니다 NULL.

널 포인터 검사는 선택적 인수가 제공되지 않은 경우 전제 조건이 충족되는지 확인하거나 적절한 조치를 취하기 위해 함수 인수 (생성자 인수 포함)에 중요하며 클래스의 내부를 노출 한 후 클래스의 불변성을 확인하는 것이 좋습니다. 그러나 포인터가 NULL이 된 유일한 이유가 버그가 존재한다면 검사 할 필요가 없습니다. 이 버그는 포인터를 다른 유효하지 않은 값으로 쉽게 설정할 수 있습니다.

내가 당신과 같은 상황에 직면했다면 나는 두 가지 질문을 할 것입니다.

  • 왜 주니어 프로그래머의 실수가 출시 전에 잡히지 않았습니까? 예를 들어 코드 검토 또는 테스트 단계에서? 결함이있는 가전 제품을 시장에 출시하는 것은 안전에 중요한 응용 분야에서 사용되는 결함이있는 장치를 출시하는 것만 큼 비용이 많이들 수 있습니다 (시장 점유율과 영업권을 고려하는 경우). 다른 QA 활동.
  • null 검사가 실패하면 어떤 오류 처리를 수행 할 것으로 예상 assert(msgSender_)됩니까 , 아니면 null 검사 대신 쓸 수 있습니까? 널 (null) 체크인 만 입력하면 충돌이 발생하지 않았지만 실제로 작업이 생략 된 상태에서 작업이 수행되었다는 전제하에 소프트웨어가 계속해서 더 나쁜 상황을 만들었을 수 있습니다. 이로 인해 소프트웨어의 다른 부분이 불안정해질 수 있습니다.

9

이 예제는 입력 매개 변수가 null †인지 여부보다 개체 수명에 관한 것 같습니다. 당신 PowerManager항상 유효한을 가져야 한다고 언급 했으므로 IMsgSender포인터로 인수를 전달하면 (널 포인터 가능성을 허용) 디자인 결함 ††으로 나에게 충격을줍니다.

이와 같은 상황에서는 호출자의 요구 사항이 언어로 적용되도록 인터페이스를 변경하는 것이 좋습니다.

PowerManager::PowerManager(const IMsgSender& msgSender)
  : msgSender_(msgSender) {}

void PowerManager::SignalShutdown() {
    msgSender_->sendMsg("shutdown()");
}

이 방법으로 다시 작성하면 전체 수명 PowerManagerIMsgSender대한 참조가 필요합니다. 이는 또한 IMsgSender보다 오래 살아야 한다는 암시 적 요구 사항을 설정하고 PowerManager내부의 널 포인터 검사 또는 어설 션의 필요성을 무시합니다 PowerManager.

스마트 포인터 (boost 또는 c ++ 11 사용)를 사용하여 동일한 내용을 작성하여 명시 적으로 IMsgSender보다 오래 살도록 할 수도 있습니다 PowerManager.

PowerManager::PowerManager(std::shared_ptr<IMsgSender> msgSender) 
  : msgSender_(msgSender) {}

void PowerManager::SignalShutdown() {
    // Here, we own a smart pointer to IMsgSender, so even if the caller
    // destroys the original pointer, we still have a valid copy
    msgSender_->sendMsg("shutdown()");
}

IMsgSender수명이 수명보다 길어질 수없는 경우 PowerManager(예 :)이 방법을 선호합니다 x = new IMsgSender(); p = new PowerManager(*x);.

† 포인터와 관련하여 : 널 (null) 널 검사는 코드를 읽기 어렵게하고 안정성을 향상시키지 않습니다 ( 안정성 모양 을 개선하여 훨씬 나쁩니다).

어딘가에 누군가가 메모리를위한 주소를 가졌다 IMsgSender. std::bad_alloc유효하지 않은 포인터를 전달하지 않도록 할당이 성공했는지 (라이브러리 리턴 값 확인 또는 예외 처리 )를 확인하는 것은 해당 함수의 책임 입니다.

PowerManager의 소유가 아니기 때문에 IMsgSender(잠시 동안 빌리는 것), 그 메모리를 할당하거나 파기 할 책임이 없습니다. 이것이 내가 참조를 선호하는 또 다른 이유입니다.

††이 직업을 처음 사용했기 때문에 기존 코드를 해킹 할 것으로 예상됩니다. 따라서 디자인 결함으로 인해 결함이 작업중 인 코드에 있음을 의미합니다. 따라서 null 포인터를 확인하지 않기 때문에 코드에 플래그를 지정하는 사람들은 실제로 포인터가 필요한 코드 작성에 대한 플래그를 지정하고 있습니다. :)


1
때로는 원시 포인터를 사용하는 데 아무런 문제가 없습니다. Std :: shared_ptr은 은색 총알이 아니며 인수 목록에 사용하는 것은 조잡한 엔지니어링의 치료법입니다. 가능한 한 항상 그것을 사용하는 것이 '간단한'것으로 생각됩니다. 그런 느낌이 든다면 Java를 사용하십시오.
제임스

2
나는 원시 포인터가 나쁘다고 말하지 않았다! 그들은 분명히 자신의 자리를 가지고 있습니다. 그러나 이것은 C + + 이므로 참조 (및 현재 공유 포인터)는 이유로 언어의 일부입니다. 그것들을 사용하면 성공하게 될 것입니다.
세스

나는 똑똑한 포인터 아이디어를 좋아하지만이 특정 공연에서는 옵션이 아닙니다. 참조 사용을 권장하는 +1 그러나 닭과 계란의 의존성 문제로 인해, 예를 들어 주사의 대상이 될 수있는 물체에 홀더에 대한 포인터 (또는 참조)가 주입되어 있어야하는 경우에는 참조를 주입 할 수없는 경우가 있습니다. 이것은 때때로 밀접하게 결합 된 디자인을 나타낼 수 있습니다. 그러나 객체와 객체의 반복자와의 관계 에서처럼 전자를 사용하지 않고 후자를 사용하는 것이 의미가없는 경우가 종종 있습니다.
evadeflow

8

예외와 마찬가지로 보호 조건은 오류에서 복구 할 작업을 알고 있거나보다 의미있는 예외 메시지를 제공하려는 경우에만 유용합니다.

오류를 삼키는 것 (예외 또는 가드 검사로 잡 히든)은 오류가 중요하지 않은 경우에만 수행해야합니다. 오류가 발생하는 것을 볼 수있는 가장 일반적인 장소는 오류 로깅 코드입니다. 상태 메시지를 기록 할 수 없어서 앱을 중단하고 싶지 않습니다.

함수가 호출 될 때마다 그리고 선택적인 동작이 아니며, 자동으로 실패하지 않고 크게 실패해야합니다.

편집하다: 주니어 프로그래머 이야기에 대해 생각할 때 개인 구성원이 절대로 허용되지 않았을 때 null로 설정되었다는 것이 들렸습니다. 부적절한 글쓰기에 문제가 있었으며 읽을 때 유효성을 검사하여 수정하려고합니다. 이것은 거꾸로입니다. 식별 할 때까지 이미 오류가 발생했습니다. 이를위한 코드 검토 / 컴파일러 강제 솔루션은 보호 조건이 아니라 getter 및 setter 또는 const 멤버입니다.


7

다른 사람들이 지적했듯이, 이것은 합법적으로msgSender 할 수 있는지 여부에 달려 있습니다 . 다음은 가정해야한다고 가정합니다. NULL NULL 아니 합니다.

void PowerManager::SignalShutdown()
{
    if (!msgSender_)
    {
       throw SignalException("Shut down failed because message sender is not set.");
    }

    msgSender_->sendMsg("shutdown()");
}

팀의 다른 사람들이 제안한 "수정"은 Dead Programs Tell No Lies 원칙에 위배됩니다. 버그는 실제로 찾기가 어렵습니다. 이전 문제를 기반으로 동작을 자동으로 변경하는 방법은 첫 번째 버그를 찾기 어렵게 할뿐만 아니라 자체의 두 번째 버그도 추가합니다.

주니어는 null을 확인하지 않아 혼란을 겪었습니다. 이 코드 조각이 정의되지 않은 상태로 계속 실행하여 혼란을 겪는다면 (장치는 켜져 있지만 프로그램은 "감지"합니다)? 아마도 프로그램의 다른 부분은 장치가 꺼져있을 때만 안전한 것을 할 것입니다.

이러한 접근 방식 중 하나는 자동 실패를 방지합니다.

  1. 이 답변에서 제안한대로 어설 션을 사용하십시오. 에서 하지만 프로덕션 코드에서 설정되어 있는지 확인하십시오. 물론 다른 생산자들이 생산에서 제외 될 것이라는 가정하에 작성된 경우 문제가 발생할 수 있습니다.

  2. null 인 경우는 예외를 throw합니다.


5

생성자에서 null을 잡는 것에 동의합니다. 또한 멤버가 헤더에서 다음과 같이 선언 된 경우 :

IMsgSender* const msgSender_;

그런 다음 초기화 후에 포인터를 변경할 수 없으므로 구성에 문제가 없으면 포인터가 포함 된 객체의 수명 동안 괜찮습니다. (목적은 지적 것입니다 하지 CONST합니다.)


훌륭한 조언, 감사합니다! 사실, 유감스럽게도 더 이상 투표 할 수 없어서 유감입니다. 명시된 질문에 대한 직접적인 대답은 아니지만 '우연히'포인터가 될 가능성을 제거 하는 좋은 방법 NULL입니다.
evadeflow

@evadeflow 나는 "다른 사람들과 동의한다"는 질문의 주된 답으로 생각했다. 건배! ;-)
Grimm The Opiner

질문이 표현 된 방식을 고려할 때이 시점에서 허용되는 답변을 변경하는 것은 약간 무례한 것입니다. 그러나 나는 정말로이 조언이 탁월하다고 생각하며, 그것을 스스로 생각하지 않아서 유감입니다. const포인터를 사용하면 assert()생성자 외부에 있을 필요 가 없으므로이 답변은 @Kristof Provost의 것보다 조금 더 나아 보입니다 (이 또한 뛰어 났지만 질문을 간접적으로 해결했습니다). 다른 사람들이 이것을 투표하기를 바랍니다. assert그냥 포인터를 만들 수있을 때 모든 방법에서 의미가 없습니다 const.
evadeflow

@evadeflow 사람들은 담당자를 위해서만 Questins를 방문합니다. 이제 아무도 다시 보지 않을 것입니다. ;-) 나는 그것이 포인터에 관한 질문이기 때문에 튀어 나왔고 나는 피의 "항상 모든 것을 위해 스마트 포인터를 사용하라"여단을 주시하고있다.
Ommer Grimm

3

이것은 명백한 위험입니다!

나는 C 코드베이스에서 선임 개발자로 일하면서 같은 포인터를 똑같이 밀고 모든 포인터가 맹목적으로 null인지 검사하기 위해 가장 "표준"으로 작업했습니다. 개발자는 다음과 같은 작업을 수행하게됩니다.

// Pre: vertex should never be null.
void transform_vertex(Vertex* vertex, ...)
{
    // Inserted by my "wise" co-worker.
    if (!vertex)
        return;
    ...
}

나는 그런 기능에서 전제 조건에 대한 검사를 한 번 제거하고 assert어떤 일이 일어날 지 알기 위해 그것을 대체하려고 시도했습니다 .

공포에 비추어 볼 때 코드베이스 에서이 함수에 null을 전달하는 수천 줄의 코드를 찾았지만 개발자가 혼란스러워 일할 때까지 더 많은 코드를 추가 한 곳이 있습니다.

추가 공포로,이 문제는 코드베이스의 null을 검사하는 모든 종류의 장소에서 널리 퍼졌습니다. 코드베이스는 가장 명확하게 문서화 된 전제 조건을 자동으로 위반하기 위해 이러한 검사에 의존하기 위해 수십 년 동안 성장했습니다. 이 치명적인 수표를 제거 asserts하여 코드베이스에서 수십 년 동안 발생한 모든 논리적 인적 오류가 드러날 것입니다.

이 + 시간과 같이 겉보기에 순진한 두 줄의 코드와 팀이 수천 개의 누적 된 버그를 감추었습니다.

이것들은 소프트웨어가 작동하기 위해 존재하는 다른 버그에 따라 버그를 만드는 관행입니다. 그것은이다 악몽의 시나리오 . 또한 이러한 전제 조건을 위반하는 것과 관련된 모든 논리적 오류는 실수가 발생한 실제 사이트에서 백만 줄의 코드를 신비하게 표시합니다.이 모든 null 검사는 버그를 숨기고 잊어 버린 장소에 도달 할 때까지 버그를 숨기므로 버그를 숨길 수 있습니다.

null 포인터가 전제 조건을 위반하는 모든 곳에서 단순히 맹목적으로 null을 확인하는 것은 소프트웨어가 어설 션 오류 및 프로덕션 충돌에 대해 미션 크리티컬하지 않은 한이 시나리오의 가능성이 바람직하지 않은 한 전제 조건입니다.

코딩 표준이 함수에서 역 참조 된 모든 단일 포인터에 대해 먼저 개인 데이터 멤버까지 NULL을 검사하도록 요구하는 것이 합리적입니까?

그래서 절대 말하지 않겠습니다. "안전"하지도 않습니다. 그 반대 일 수도 있고 코드베이스 전체에서 모든 종류의 버그를 가려서 수년에 걸쳐 가장 끔찍한 시나리오로 이어질 수 있습니다.

assert여기가는 길입니다. 전제 조건의 위반이 눈에 띄지 않게해서는 안됩니다. 그렇지 않으면 머피의 법칙이 쉽게 시작될 수 있습니다.


1
"어설 션 (assert)"은 갈 길입니다. 그러나 현대의 모든 시스템에서 포인터를 통한 각 메모리 액세스에는 하드웨어에 널 포인터 어설 션이 내장되어 있습니다. 따라서 "assert (p! = NULL); return * p;"에서 assert도 대부분 무의미합니다.
gnasher729

@ gnasher729 아, 그것은 아주 좋은 지적이지만 assert, 전제 조건이 무엇인지를 확립하고 중단을 강요하는 방법이 아닌 문서 메커니즘으로 보는 경향이 있습니다 . 그러나 어떤 종류의 코드가 오류를 일으켰고 어설 션 조건이 실패했는지 ( "충돌 문서화") 정확하게 보여주는 어설 션 오류의 특성과도 비슷합니다. 우리가 디버거 외부에서 디버그 빌드를 사용하고 결국 막을 내리면 편리합니다.

@ gnasher729 아니면 그 일부를 오해했을 수도 있습니다. 이러한 최신 시스템은 어설 션이 실패한 소스 코드 라인을 보여줍니까? 나는 일반적으로 그 시점에서 정보를 잃어 버렸고 segfault / 액세스 위반을 나타낼 수 있다고 생각하지만 "현대"와는 거리가 멀습니다. 여전히 내 개발의 대부분은 Windows 7에서 완고하게 이루어졌습니다.

예를 들어, MacOS X 및 iOS에서 개발 중에 널 포인터 액세스로 인해 앱이 충돌하는 경우 디버거는 정확한 코드 줄을 알려줍니다. 또 다른 이상한 측면이 있습니다. 의심스러운 작업을 수행하면 컴파일러에서 경고를 표시합니다. 함수에 포인터를 전달하고 액세스하면 컴파일러는 포인터가 NULL 일 수 있다는 경고를 표시하지 않습니다. 포인터가 너무 자주 발생하기 때문에 경고에 빠질 수 있습니다. 그러나 (p == NULL) ... 인 경우 비교를 수행하면 컴파일러는 p가 NULL 일 가능성이 높다고 가정합니다.
gnasher729

다른 방법으로 테스트 한 이유는 무엇입니까? 따라서 테스트하지 않고 사용하면 경고 메시지가 표시됩니다. 만약 당신이 당신의 assert-like macro를 사용한다면, "my_assert (p! = NULL,"널 포인터, 바보! "); * p = 1;" 경고하지 않습니다.
gnasher729

2

예를 들어 Objective-Cnil객체의 모든 메서드 호출을 0이 아닌 값으로 평가되는 no-op로 처리합니다. 귀하의 질문에 제안 된 이유로 Objective-C의 설계 결정에는 몇 가지 장점이 있습니다. 모든 메소드 호출을 널 (null) 보호하는 이론적 개념은 잘 공개되고 지속적으로 적용된다면 장점이 있습니다.

즉, 코드는 진공 상태가 아닌 생태계에 존재합니다. null-guarded 동작은 비 아이디어적이고 놀랍습니다. C ++에, 따라서 유해 고려되어야한다. 요약하면, 아무도 C ++ 코드를 그런 식으로 쓰지 않으므로 그렇게 하지 마십시오! 카운터 - 예를 들어,하지만, 그 호출주의 free()또는 deleteA의 NULLC 및 C ++는 어떤 조합 수 없습니다 보장됩니다.


귀하의 예에서, msgSendernull이 아닌 생성자에 어설 션을 배치하는 것이 좋습니다 . 생성자 가 바로 메서드호출 한msgSender 경우 어쨌든 충돌 할 수 있으므로 이러한 어설 션이 필요하지 않습니다. 그러나 나중에 사용 하기 msgSender 위해 저장 하기 SignalShutdown()때문에 값이 어떻게되는지 에 대한 스택 추적을 보면 분명하지 않으므로 NULL생성자에 대한 어설 션으로 디버깅이 훨씬 쉬워집니다.

더 좋은 것은 생성자가 const IMsgSender&참조를 허용해야한다는 것입니다 NULL.


1

null 역 참조를 피하라는 요청을받는 이유는 코드가 강력하다는 것입니다. 오래 전에 주니어 프로그래머의 예는 단지 예일뿐입니다. 누구나 실수로 코드를 중단하고 특히 전역 및 클래스 전역에 대해 null 역 참조를 유발할 수 있습니다. C 및 C ++에서는 직접 메모리 관리 기능을 통해 실수로 더 많은 가능성을 얻을 수 있습니다. 당신은 놀랄지도 모르지만 이런 종류의 일은 매우 자주 발생합니다. 지식이 풍부하고 경험이 많으며 고위 개발자들조차도.

모든 것을 null로 검사 할 필요는 없지만 null 일 가능성이있는 역 참조로부터 보호해야합니다. 이는 일반적으로 다른 기능에서 할당, 사용 및 역 참조 될 때 발생합니다. 다른 기능 중 하나가 수정되어 기능이 손상 될 수 있습니다. 다른 함수 중 하나가 순서대로 호출되지 않을 수도 있습니다 (소멸자와 별도로 호출 할 수있는 할당 취소 기가있는 경우).

나는 당신의 동료들이 당신에게 assert를 사용하는 것과 당신에게 말하는 접근법을 선호합니다. 테스트 환경에서 충돌이 발생하여 프로덕션 환경에서 정상적으로 수정하고 실패하는 데 문제가 있음이 더 분명합니다.

또한 커버리지 또는 강화와 같은 강력한 코드 정확성 도구를 사용해야합니다. 그리고 모든 컴파일러 경고를 해결해야합니다.

편집 : 다른 사람들이 언급했듯이 여러 코드 예제에서와 같이 자동 실패는 일반적으로 잘못된 것입니다. 함수가 null 값에서 복구 할 수없는 경우 호출자에게 오류를 반환하거나 예외를 발생시켜야합니다. 호출자는 호출 순서를 수정하거나, 호출자에게 오류를 리턴하거나 (또는 ​​예외를 던지는 등) 책임을집니다. 결과적으로 함수가 정상적으로 복구 및 이동, 정상적으로 복구 및 실패 (예 : 한 명의 사용자에 대한 내부 오류로 인해 트랜잭션이 실패하지만 완전히 종료되지 않음)하거나 함수가 애플리케이션 상태가 손상되었다고 판별 함 복구 할 수없고 응용 프로그램이 종료됩니다.


용기가 (비교적으로) 인기없는 위치를지지하게해서 +1. 경우 실망 했 아무도 (그들이있어이 내 동료를 방어하지 아주 여러 해 동안 품질의 제품을 내놓고있다 똑똑한 사람들). 또한 앱이 "테스트 환경에서 충돌하고 프로덕션 환경에서 정상적으로 실패"한다는 아이디어가 마음에 듭니다. 나는 'rote' NULL체크 를 의무화하는 것이 그 방법 이라는 것을 확신 하지 못하지만, 기본적으로 "Aigh. 너무 불합리한 것 같지 않다"고 말하는 직장 ​​밖에서 누군가의 의견을 듣는 것에 감사 합니다.
evadeflow

malloc () 후에 사람들이 NULL을 테스트하는 것을 보았을 때 짜증이납니다. 최신 OS가 메모리를 관리하는 방법을 이해하지 못한다고 말합니다.
Kristof Provost

2
내 생각에 @KristofProvost는 malloc이 항상 성공하는 오버 커밋을 사용하는 OS에 대해 이야기하고 있습니다 (그러나 실제로 사용 가능한 메모리가 충분하지 않으면 OS가 나중에 프로세스를 종료 할 수 있음). 그러나 이것이 malloc에서 널 검사를 건너 뛰는 좋은 이유는 아닙니다. 오버 커밋은 플랫폼에서 보편적이지 않으며 활성화되어 있어도 malloc이 실패하는 다른 이유가있을 수 있습니다 (예 : Linux의 경우 ulimit로 설정된 프로세스 주소 공간 제한). ).
존 Bartholomew

1
요한이 한 말. 나는 과잉 커밋에 대해 실제로 이야기하고있었습니다. 초과 커밋은 Linux에서 기본적으로 활성화되어 있습니다 (이것은 청구서를 지불합니다). 잘 모르겠지만 Windows에서 동일하다면 의심됩니다. 거의 모든 코드 를 작성할 준비가되어 있지 않으면 거의 일어나지 않는 오류를 처리하기 위해 테스트를 거치지 않는 한 대부분 의 경우 어쨌든 충돌 이 발생합니다 ...
Kristof Provost

2
메모리 부족 상태를 실제로 올바르게 처리하는 코드 (메모리 부족이 실제로 가능한 리눅스 커널 외부)를 본 적이 없다고 덧붙여 보겠습니다 . 내가 본 모든 코드는 나중에 나중에 충돌합니다. 달성 한 모든 것은 시간을 낭비하고 코드를 이해하고 실제 문제를 숨기기 어렵게 만드는 것입니다. 널 역 참조는 디버그하기 쉽다 (핵심 파일이있는 경우).
Kristof Provost 2011

1

a의 사용 포인터 대신에의 참조는 것을 말해 것 msgSender입니다 만 옵션 여기에 NULL 체크를 잘 할 것이다. 코드 스 니펫이이를 결정하기에는 너무 느립니다. 어쩌면 PowerManager가치있는 (또는 테스트 가능한) 다른 요소가있을 수 있습니다 ...

포인터와 참조를 선택할 때 두 옵션을 모두 철저히 평가합니다. 멤버 (개인 멤버의 경우에도)에 대한 포인터를 사용해야하는 경우, if (x)참조 해제 할 때마다 절차 를 수락해야 합니다.


다른 의견에서 언급했듯이 참조를 주입 할 수없는 경우가 있습니다. 내가 많이
부딪친

@evadeflow : 자백합니다. 역 참조 전에 확인하는지 여부는 클래스 크기와 포인터 멤버의 가시성 (참조 할 수 없음)에 달려 있습니다. 클래스가 작고 평범하고 포인터가 비공개 인 경우, 나는하지 않습니다.
Wolf

0

그것은 당신이 일어나고 싶은 것에 달려 있습니다.

귀하는 "소비자 전자 장치"에서 작업하고 있다고 썼습니다. 으로 설정 msgSender_하여 버그가 발생 NULL하면 원하는 경우

  • 장치가 SignalShutdown계속 작동하고 나머지 작업은 건너 뛰지 만 계속하거나
  • 장치가 충돌하여 사용자가 다시 시작하도록 강요합니까?

전송되지 않는 종료 신호의 영향에 따라 옵션 1을 선택할 있습니다. 사용자가 자신의 음악을 듣고 계속할 수 있지만 디스플레이가 여전히 이전 트랙의 제목을 표시하는 경우, 그 수있는 장치의 전체 충돌하는 것이 바람직합니다.

물론 옵션 1을 선택하면 개발 중에 눈에 띄지 않는 버그가 발생할 가능성을 줄이기 위해 ( assert다른 사람들이 권장하는대로)가 중요 합니다. if널 가드 생산 사용 장애 완화를 위해 단지가있다.

개인적으로 저는 프로덕션 빌드에 "충돌 초기"접근 방식을 선호하지만 버그가 발생하면 쉽게 수정하고 업데이트 할 수있는 비즈니스 소프트웨어를 개발하고 있습니다. 가전 ​​제품의 경우 이것은 쉽지 않을 수 있습니다.

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