2008-09-01 4 views
23

assert()을 사용하고 어설 션이 실패하면 assert()abort()을 호출하여 실행중인 프로그램을 갑자기 종료합니다. 나는 생산 코드에서 그것을 감당할 수 없다. 런타임에 어설 션 할 수있는 방법이 있습니까? 그렇다면 실패한 어설 션을 잡아서 정상적으로 처리 할 수있는 기회가 있습니까?abort()를 사용하지 않고 어떻게 assert() 할 수 있습니까?

답변

23

예, 실제로 있습니다. C++의 assert()은 정확하게 C의 assert()이고, abort()의 "feature"가 번들 된 사용자 정의 어설 션 함수를 작성해야합니다. 다행히도 이것은 매우 간단합니다.

Assert.hh

template <typename X, typename A> 
inline void Assert(A assertion) 
{ 
    if(!assertion) throw X(); 
} 

술어가 보유하지 않는 경우 위의 함수는 예외가 발생합니다. 그러면 예외를 잡을 기회가 생깁니다. 예외가 발생하지 않으면 terminate()이 호출되어 abort()과 비슷한 프로그램이 종료됩니다.

생산을 위해 제작할 때 어설 션을 최적화하는 방법에 대해 궁금해 할 수 있습니다. 이 경우, 프로덕션을 위해 빌드하고있는 것을 나타내는 상수를 정의한 다음 Assert()을 참조하면 상수를 참조 할 수 있습니다.

debug.hh

#ifdef NDEBUG 
    const bool CHECK_WRONG = false; 
#else 
    const bool CHECK_WRONG = true; 
#endif 

main.cc

#include<iostream> 

struct Wrong { }; 

int main() 
{ 
    try { 
     Assert<Wrong>(!CHECK_WRONG || 2 + 2 == 5); 
     std::cout << "I can go to sleep now.\n"; 
    } 
    catch(Wrong e) { 
     std::cerr << "Someone is wrong on the internet!\n"; 
    } 

    return 0; 
} 

CHECK_WRONG 다음 Assert() 호출이 명제는 인 경우에도 거리 생산 컴파일 될 일정하면 상수 표현이 아닙니다. CHECK_WRONG을 참조하면 좀 더 많은 단점이 있습니다. 그러나 우리는 다양한 어설 션 그룹을 분류하고 각 그룹을 사용 가능하게하거나 사용 불가능하게 할 수 있다는 점에서 이점을 얻습니다. 예를 들어 프로덕션 코드에서 사용 가능하게하려는 어설 션 그룹을 정의한 다음 개발 빌드에서만 볼 수있는 어설 션 그룹을 정의 할 수 있습니다.

Assert() 기능은

if(!assertion) throw X(); 

를 입력하는 것과 같습니다 그러나 그것은 명확하게 프로그래머의 의도를 나타냅니다 주장을합니다. 어노테이션은 일반적인 assert()과 마찬가지로이 접근법으로 grep하기 쉽습니다.

이 기술에 대한 자세한 내용은 Bjarne Stroustrup의 The C++ Programming Language 3e, 24.3.7.2 절을 참조하십시오.

+0

당신이 어떤 런타임 데이터를 전송할 수 없기 때문에 당신이 건설 예외 기본이 훨씬 혜택이 아니다 얻을 오직 할 수 있기 때문에 catch 사이트 ... –

+0

int를 만들기보다는 assert 인수를 templatizing하는 것의 부가 가치는 무엇입니까? 결국 어쨌든 int로 변환되어야합니다. – JohnMcG

+0

@Greg Rogers Bjarne Stroustrup은 언급 한 문제에 대한 해결책을 제공합니다. Throw하려는 사용자 정의 된 객체를 Assert()의 추가 매개 변수로 추가 할 수 있습니다. 단점은 추가 매개 변수 때문에 Assert()가 assert()처럼 보이지 않는다는 것입니다. – wilhelmtell

6

C/C++의 어설 션은 디버그 빌드에서만 실행됩니다. 그래서 이것은 런타임에 발생하지 않습니다. 일반적으로 어설 션은 버그가 있음을 표시하고 버그가 있음을 표시해야하며 일반적으로 코드 등에 가정을 표시해야합니다.

런타임시 오류를 검사하는 코드가 필요할 경우 이것이 그들이 의도 한대로 주장합니다. 귀하의 대답은 기본적으로 어설 션 구문에서 예외 처리기를 래핑합니다. 이 방법이 효과적 일지는 모르겠지만 예외적으로 던지기 만하면 특별한 이점이 없습니다.

+0

프로덕션 빌드에서 실행 중이거나 실행 중이 아닌 어설 션은 컴파일러 옵션입니다. 최적화 옵션에 관계없이 gcc에서 기본적으로 실행됩니다. –

+2

개인적으로 릴리스 모드에서 주장을 유지하는 것이 좋습니다. 클라이언트가 응용 프로그램을 사용할 때 임의의 충돌을 선호 할 수 있습니다 ... –

+2

특히 어설 션이 거짓이되면 프로그램에서 정의되지 않은 동작으로 이어지는 경우, 그것은 클라이언트가 적어도 더 가치있는 에러 메시지와 쉬운 재생 단계를 제공 할 수있게 해줄 것입니다; 그것은 처음에는 결코 일어나지 않는다는 것을 고려하면; 그리고 당신은 그것을 보장하지 않을 것이라고 생각했습니다. – dcousens

10

glib's error reporting functions은 어설 션 후에 계속됩니다. glib은 Gnome (GTK를 통해)이 사용하는 기본 플랫폼 독립 라이브러리입니다. 전제 조건을 검사하고 전제 조건이 실패 할 경우 스택 추적을 인쇄하는 매크로가 있습니다.

char *doSomething(char *ptr) 
{ 
    RETURN_VAL_IF_FAIL(ptr != NULL, NULL); // same as assert(ptr != NULL), but returns NULL if it fails. 

    if(ptr != NULL)  // Necessary if you want to define the macro only for debug builds 
    { 
     ... 
    } 

    return ptr; 
} 

void doSomethingElse(char *ptr) 
{ 
    RETURN_IF_FAIL(ptr != NULL); 
} 
:

void print_stack_trace(int fd) 
{ 
    void *array[256]; 
    size_t size; 

    size = backtrace (array, 256); 
    backtrace_symbols_fd(array, size, fd); 
} 

이 당신이 매크로를 사용할 줄 방법은 다음과 같습니다 여기

#define RETURN_IF_FAIL(expr)  do {     \ 
if (!(expr))           \ 
{              \ 
     fprintf(stderr,        \ 
       "file %s: line %d (%s): precondition `%s' failed.", \ 
       __FILE__,           \ 
       __LINE__,           \ 
       __PRETTY_FUNCTION__,        \ 
       #expr);            \ 
     print_stack_trace(2);          \ 
     return;             \ 
};    } while(0) 
#define RETURN_VAL_IF_FAIL(expr, val) do {       \ 
if (!(expr))              \ 
{                 \ 
     fprintf(stderr,            \ 
       "file %s: line %d (%s): precondition `%s' failed.",  \ 
       __FILE__,            \ 
       __LINE__,            \ 
       __PRETTY_FUNCTION__,         \ 
       #expr);             \ 
     print_stack_trace(2);           \ 
     return val;             \ 
};    } while(0) 

는 GNU 툴 체인 (GCC)를 사용하는 환경 용으로 작성된 스택 트레이스를 출력하는 기능,이다 여기
+3

@wilhelmtell I 내가 예외를 사용하는 것에 동의하지 않기 때문에 이것이 지금까지 최고의 해결책이라고 생각하십시오. Exception은 예외적 인 것이어야하지만 Assert를 처리하는 것은 예외가 아닙니다. 또한 릴리스 빌드에서 Asserts On을 떠날 계획이라면 성능이 중요하지 않으면해야한다고 생각합니다.이 방법이 더 효율적입니다. 개인화 할 수도 있으므로 프로그램이 종료 될 때 종료하지 않아도 정보를 다시 얻을 수 있습니다. 나는 Asshors and Exceptions에 대한 The Pragmatic Programmer 책의 장을 읽을 것을 제안한다. 플러스 [이 링크] (http://www.gotw.ca/publications/mill22.htm). – ForceMagic

5

은 (맥 OS 10.4) 내에서 "assert.h를"이 작업은 다음과 같습니다

#define assert(e) ((void) ((e) ? 0 : __assert (#e, __FILE__, __LINE__))) 
#define __assert(e, file, line) ((void)printf ("%s:%u: failed assertion `%s'\n", file, line, e), abort(), 0) 

이것을 기반으로 abort()에 대한 호출을 throw (예외)로 바꿉니다. printf 대신 문자열을 예외의 오류 메시지로 포맷 할 수 있습니다. 결국, 당신은 다음과 같은 것을 얻습니다 :

#define assert(e) ((void) ((e) ? 0 : my_assert (#e, __FILE__, __LINE__))) 
#define my_assert(e, file, line) (throw std::runtime_error(\ 
    std::string(file:)+boost::lexical_cast<std::string>(line)+": failed assertion "+e)) 

나는 그것을 컴파일하려하지 않았지만, 당신은 의미를 얻습니다.

참고 : "예외"헤더가 항상 포함되어 있는지와 부스트 (오류 메시지의 형식 지정에 사용할 경우)를 확인해야합니다. 그러나 "my_assert"를 함수로 만들고 프로토 타입 만 선언 할 수도 있습니다. 당신이 자유롭게 필요한 모든 헤더를 포함 할 수

void my_assert(const char* e, const char* file, int line); 

그리고 어딘가에 구현 : 같은 뭔가.

필요할 경우 #ifdef DEBUG에서 감싸 주거나 항상이 검사를 실행하려는 경우가 아닙니다.

+0

그래서 매크로가 템플릿 함수보다 낫다는 주장은 여전히 ​​남아 있습니다. 또한 시퀀스 연산자는 임의의 순서로 피연산자를 수행 할 수 있음을 의미합니다. 이것이 __asert()에서 의미하는 바가 아닌지 의심 스럽습니다. – wilhelmtell

+0

첫 번째 블록의 "__assert"함수는 시스템 헤더 (0S 10.4)에서 온 것입니다. 매크로를 사용하면 FILE 및 LINE 정보를보다 쉽게 ​​얻을 수 있습니다. 그러나 어떤 시점에서 그것은 꽤 화장을합니다 ... – rlerallut

-1
_set_error_mode(_OUT_TO_MSGBOX); 

나를 믿어주세요.이 기능을 사용하면 도움이됩니다.

+0

이것은 Windows 전용 기능인 것처럼 보입니다 : [link] (https://msdn.microsoft.com/en-us/library/aa235388(v=60)). aspx) –

+0

그리고 적어도 당신은 헤더/컴파일러/무엇이 그것을 사용하는 데 필요한 설명해야합니다. –

관련 문제