C ++에서 assert ()를 사용하고 있습니까?


92

릴리스 빌드의 성능에 영향을주지 않고 디버깅을 더 쉽게하기 위해 내 C ++ 코드에 많은 어설 션을 추가하는 경향이 있습니다. 이제는 assertC ++ 메커니즘을 염두에 두지 않고 설계된 순수 C 매크로입니다.

반면에 C ++ std::logic_error는 프로그램의 논리 (따라서 이름)에 오류가있는 경우 throw되는을 정의합니다 . 인스턴스를 던지는 것은 assert.

문제이다 assert하고 abort, 소멸자를 호출하지 않고 즉시 프로그램을 종료 때문에 예외를 발생하는 반면 수동 런타임 불필요한 비용을 추가 정리 스킵 둘. 이 문제를 해결하는 한 가지 방법 SAFE_ASSERT은 C와 동일하게 작동하지만 실패시 예외를 던지는 자체 assertion 매크로를 만드는 것 입니다.

이 문제에 대한 세 가지 의견을 생각할 수 있습니다.

  • C의 주장을 고수하십시오. 프로그램이 즉시 종료되기 때문에 변경 사항이 올바르게 풀 렸는지 여부는 중요하지 않습니다. 또한 #defineC ++에서 s를 사용 하는 것도 나쁘다.
  • 예외를 던지고 main ()에서 포착하십시오 . 코드가 프로그램의 모든 상태에서 소멸자를 건너 뛰도록 허용하는 것은 나쁜 습관이며 어떤 대가를 치르더라도 피해야하며, terminate () 호출도 마찬가지입니다. 예외가 발생하면 포착해야합니다.
  • 예외를 발생시키고 프로그램을 종료 시키십시오. 프로그램을 종료하는 예외는 괜찮으며으로 인해 NDEBUG릴리스 빌드에서는 절대 발생하지 않습니다. 캐칭은 불필요하며 내부 코드의 구현 세부 정보를 main().

이 문제에 대한 확실한 답이 있습니까? 전문적인 참조가 있습니까?

편집 됨 : 소멸자를 건너 뛰는 것은 물론 정의되지 않은 동작이 아닙니다.


22
아니, 정말 logic_error논리 오류입니다. 프로그램 로직의 오류를 버그라고합니다. 예외를 던져서 버그를 해결하는 것은 아닙니다.
R. Martinho Fernandes

4
어설 션, 예외, 오류 코드. 각각은 완전히 다른 사용 사례를 가지고 있으며 다른 것이 필요한 곳에 사용해서는 안됩니다.
Kerrek SB

5
static_assert가능한 경우 적절한 곳에서 사용하십시오 .
Flexo

4
@trion 나는 그것이 어떻게 도움이되는지 보지 못합니다. 던질 std::bug래요?
R. Martinho Fernandes

3
@ 트리 온 : 그러지 마. 예외는 디버깅을위한 것이 아닙니다. 누군가 예외를 잡을 수 있습니다. 전화 할 때 UB에 대해 걱정할 필요가 없습니다 std::abort(). 프로세스를 종료시키는 신호를 발생시킵니다.
Kerrek SB

답변:


73

어설 션은 C ++ 코드에서 전적으로 적절합니다. 예외 및 기타 오류 처리 메커니즘은 실제로 어설 션과 동일한 용도로 사용되지 않습니다.

오류 처리는 오류를 복구하거나 사용자에게 멋지게보고 할 수있는 가능성이있는 경우입니다. 예를 들어 입력 파일을 읽는 중에 오류가 발생하면 이에 대해 조치를 취할 수 있습니다. 오류는 버그로 인해 발생할 수 있지만 주어진 입력에 대한 적절한 출력 일 수도 있습니다.

어설 션은 API가 정상적으로 확인되지 않을 때 API의 요구 사항이 충족되는지 확인하거나 개발자가 구성에 의해 보장된다고 생각하는 사항을 확인하는 것과 같은 작업입니다. 예를 들어 알고리즘에 정렬 된 입력이 필요한 경우 일반적으로 확인하지 않지만 디버그 빌드가 해당 종류의 버그에 플래그를 지정하도록 확인하는 어설 션이있을 수 있습니다. 어설 션은 항상 잘못 작동하는 프로그램을 나타내야합니다.


비정상 종료로 인해 문제가 발생할 수있는 프로그램을 작성하는 경우 어설 션을 피하는 것이 좋습니다. C ++ 언어 측면에서 엄격하게 정의되지 않은 동작은 여기에서 그러한 문제로 간주되지 않습니다. 어설 션에 도달하는 것은 이미 정의되지 않은 동작의 결과이거나 일부 정리가 제대로 작동하지 못하게 할 수있는 다른 요구 사항의 위반 일 수 있기 때문입니다.

또한 예외의 관점에서 어설 션을 구현하면 어설 션의 목적과 모순되는 경우에도 잠재적으로 포착되어 '처리'될 수 있습니다.


1
이것이 답변에 구체적으로 명시되어 있는지 확실하지 않으므로 여기에 설명하겠습니다. 코드를 작성할 때 결정할 수없는 사용자 입력과 관련된 모든 것에 대해 어설 션을 사용해서는 안됩니다. 사용자가 코드 3대신 전달 하는 경우 1일반적으로 어설 션을 트리거하지 않아야합니다. 어설 션은 프로그래머 오류 일뿐 라이브러리 사용자 또는 응용 프로그램 오류가 아닙니다.
SS Anne

101
  • 어설 션은 디버깅 을위한 것 입니다. 배송 된 코드의 사용자는 코드를 볼 수 없습니다. 어설 션이 적중되면 코드를 수정해야합니다.

    CWE-617 : 도달 가능한 주장

제품에 공격자에 의해 트리거 될 수있는 assert () 또는 유사한 문이 포함되어있어 애플리케이션 종료 또는 필요 이상으로 심각한 기타 동작이 발생합니다.

어설 션은 논리 오류를 포착하고 더 심각한 취약성 조건에 도달 할 가능성을 줄이는 데 유용하지만 여전히 서비스 거부로 이어질 수 있습니다.

예를 들어, 서버가 여러 동시 연결을 처리하고 하나의 단일 연결에서 assert ()가 발생하여 다른 모든 연결이 끊어지는 경우 서비스 거부로 이어지는 도달 가능한 어설 션입니다.

  • 예외는 예외적 인 상황 입니다. 하나가 발생하면 사용자는 원하는 작업을 수행 할 수 없지만 다른 곳에서 다시 시작할 수 있습니다.

  • 오류 처리는 정상적인 프로그램 흐름을위한 것입니다. 예를 들어, 사용자에게 숫자를 입력하고 구문 분석 할 수없는 것을 얻는다면 이는 정상적인 현상입니다 . 사용자 입력은 사용자가 제어 할 수 없으며 당연히 가능한 모든 상황을 항상 처리해야하기 때문입니다. (예 : 유효한 입력이있을 때까지 반복하여 중간에 "죄송합니다. 다시 시도"라고 말합니다.)


1
이 재 주장을 찾고 왔습니다. 프로덕션 코드로 전달되는 모든 형태의 어설 션은 잘못된 설계 및 QA를 가리 킵니다. 어설 션이 호출되는 지점은 오류 조건을 적절하게 처리해야하는 지점입니다. (저는 assert를 사용 하지 않습니다 ). 예외에 관해서는 내가 아는 유일한 유스 케이스는 ctor가 실패 할 때이고 다른 모든 것은 정상적인 오류 처리를위한 것입니다.
slashmais

5
@slashmais : 감정은 칭찬 할 만하지 만 버그가없는 완벽한 코드를 제공하지 않는 한 정의되지 않은 행동보다 선호되는 주장 (사용자를 충돌시키는 주장)을 발견했습니다. 버그는 복잡한 시스템에서 발생하며 어설 션을 사용하면 버그가 발생하는 곳을보고 진단 할 수 있습니다.
Kerrek SB

@KerrekSB 어설 션보다 예외를 사용하고 싶습니다. 적어도 코드는 실패한 분기를 버리고 다른 유용한 작업을 수행 할 기회가 있습니다. 적어도 RAII를 사용하는 경우 파일을 열기위한 모든 버퍼가 제대로 플러시됩니다.
daemonspring aug

14

어설 션은 일부 메서드 실행 전후의 내부 상태와 같은 내부 구현 불변을 확인하는 데 사용할 수 있습니다. 어설 션이 실패하면 실제로 프로그램의 논리가 손상되어 복구 할 수 없음을 의미합니다. 이 경우 최선의 방법은 사용자에게 예외를 전달하지 않고 가능한 한 빨리 중단하는 것입니다. (적어도 Linux에서는) 어설 션에 대해 정말 좋은 점은 프로세스 종료의 결과로 코어 덤프가 생성되므로 스택 추적 및 변수를 쉽게 조사 할 수 있다는 것입니다. 이것은 예외 메시지보다 논리 실패를 이해하는 데 훨씬 유용합니다.


비슷한 접근 방식이 있습니다. 나는 아마도 로컬에서 정확해야 할 논리에 대한 단언을 사용한다 (예 : 루프 불변). 예외는 로컬이 아닌 (외부) 상황 으로 인해 코드에 논리 오류가 발생한 경우입니다.
spraff

어설 션이 실패하면 프로그램 의 일부 논리 가 손상 되었음을 의미합니다 . 실패한 주장이 반드시 수행 할 수있는 것이 없음을 의미하지는 않습니다 . 고장난 플러그인이 전체 워드 프로세서를 중단해서는 안됩니다.
daemonspring

13

모든 abort ()로 인해 소멸자를 실행하지 않는 것은 정의되지 않은 동작이 아닙니다!

그렇다면 호출하는 std::terminate()것도 정의되지 않은 동작 이 될 것입니다. 그렇다면이를 제공하는 데있어 중요한 점은 무엇일까요?

assert() 어설 션은 오류 처리를위한 것이 아니라 프로그램을 즉시 중단하기위한 것입니다.


1
abort()프로그램을 즉시 중단하는 것 입니다. 하지만 assertion은 오류 처리를위한 것이 아니라는 것이 맞지만, assert는 중단하여 오류를 처리하려고합니다. 대신 예외를 던지고 가능한 경우 호출자가 오류를 처리하도록해야하지 않습니까? 결국 호출자는 한 함수의 실패로 인해 다른 작업을 수행 할 가치가 없는지 판단 할 수있는 더 나은 위치에 있습니다. 호출자가 관련되지 않은 세 가지 작업을 시도하고 있고 다른 두 작업을 완료하고이 작업을 버릴 수 있습니다.
daemonspring aug

그리고 assert호출하도록 정의됩니다 abort(조건이 거짓 인 경우). 예외를 던지는 것에 관해서는 항상 적절한 것은 아닙니다. 일부는 발신자가 처리 할 수 ​​없습니다. 호출자는 타사 라이브러리 함수의 논리 버그를 복구 할 수 있는지 또는 손상된 데이터를 수정할 수 있는지 확인할 수 없습니다.
조나단 Wakely

6

IMHO, 주장은 위반하면 다른 모든 것을 말도 안되게 만드는 조건을 확인하기위한 것입니다. 따라서 당신은 그들로부터 회복 할 수 없거나 오히려 회복은 무관합니다.

두 가지 범주로 그룹화합니다.

  • 개발자 죄 (예 : 음수 값을 반환하는 확률 함수) :

float 확률 () {return -1.0; }

assert (probability ()> = 0.0)

  • 기계가 고장났습니다 (예 : 프로그램을 실행하는 기계가 매우 잘못되었습니다) :

int x = 1;

assert (x> 0);

이것들은 모두 사소한 예이지만 현실과 그리 멀지 않습니다. 예를 들어 벡터와 함께 사용하기 위해 음의 인덱스를 반환하는 순진한 알고리즘을 생각해보십시오. 또는 맞춤형 하드웨어에 내장 된 프로그램. 또는 오히려 sh * t 발생 하기 때문에 .

그리고 그러한 개발 실수가있는 경우 구현 된 복구 또는 오류 처리 메커니즘에 대해 확신 할 수 없습니다. 하드웨어 오류에도 동일하게 적용됩니다.


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