[C++] 컴파일 시 가정의 진위를 검사하는 static_assert

반응형

실행 시 가정의 참/거짓을 검사하는 assert

다음은 두 실수를 전달받아서 나눈 결과를 반환하는 함수입니다.

double divide( double numerator, double denominator){

    if ( denominator != 0){
        return numerator / denominator;
    }
    else{
        return 0;	// denominator가 0이 될 때
    }
}

우선, 이 함수는 의도한 대로 제대로 동작합니다.

하지만, 이것을 작성한 프로그래머는 ( 그전에 조치를 취해두었기 때문에 ) 나눗셈의 분모인 denominator는 절대 0이 될 수 없다고 생각할 수도 있습니다.

그래서, 만일 denominator0이 된다면, 어떤 경우에 이런 상황이 되는지 알고 싶을 것입니다.

 

이때, 사용할 수 있는 것이 assert 전처리기 매크로( preprocessor macro )입니다.

이 매크로를 사용하기 위해서는 다음의 헤더 파일을 포함해야 합니다.

#include <cassert>;	// for assert

assert 매크로는 이 뒤에 딸려오는 표현식( expression )을 평가해서 참이 아닌 경우, 이 잘못된 표현식과 이 표현식이 사용된 파일명, 그리고 파일 내에서 표현식의 위치를 출력하고 프로그램을 중단하는 기능을 수행합니다.

#include <cassert>	// for assert

double divide( double numerator, double denominator){

    assert( denominator != 0 );  // denominator must not be zero !!

    if ( denominator != 0){
        return numerator / denominator;
    }
    else{
        return 0;
    }
}

▼출력

Assertion failed: denominator != 0, file E:\projects\c++\source\static_assert_doc.cpp, line 9

위에서, denominator0이 되는 경우, 이 assert 표현식은 거짓으로 평가되고, 이 경우 일반적으로 위와 같은 메시지를 출력하면서 프로그램을 중단하게 됩니다.

 

이것이 이 매크로의 장점으로, 문제가 발생하는 상황과 위치를 그 즉시 통보하기 때문에 디버깅이 상당히 수월해집니다.

하지만, assert의 기능은 프로그램이 예상대로 작동하고 있음을 확인하기 위한 것이지( 그래서 추후 잘못된 부분이 생긴다면 이를 수정하기 위한 것 ), 실행 시 프로그램 내에서 발생한 오류를 처리하기 위한 것이 아니고, 그럴 수도 없다는 것을 알아야 합니다.

 

참고로, assert 매크로가 메시지를 출력할 때, 아래와 같이 논리 연산자 &&를 사용하여, 추가적인 메시지를 손쉽게 덧붙일 수 있습니다.

assert( denominator != 0 && "denominator must not be zero !!" );

이것이 동작하는 이유는, 표현식을 참 / 거짓으로 평가할 때, 공백이 아닌 문자열은 항상 참으로 평가되기 때문입니다.

그래서, && 뒤의 문자열은 전체 표현식의 평가에 영향을 끼치지 못하고, 기존의 표현식의 평가만 고려하면 됩니다.

 

▼출력

Assertion failed: denominator != 0 && "denominator must not be zero !!", file E:\projects\c++\source\static_assert_doc.cpp, line 9

 

 

이러한 assert 매크로의 단점도 있는데, 그것은 assert 표현식을 프로그램 실행 시에만 평가할 수 있다는 것입니다.

그리고 이것은, 프로그램과 직접적으로 관련도 없는 코드를 평가하기 위해서, 불필요한 비용을 지불해야 한다는 것을 뜻합니다.

 

그래서, assert 표현식의 평가를 원치 않을 때는( 주로 release 버전의 컴파일을 할 때 ), 일반적으로 NDEBUG 전처리기 매크로를 사용하여, 이 기능을 제어하는 방법을 사용합니다.

#define NDEBUG	// assert 표현식을 평가하지 않도록
#include <cassert>

double divide( double numerator, double denominator){

    // 이 경우 아래의 문장은 평가되지 않습니다.
    assert( denominator != 0 && "denominator must not be zero !!" );

    if ( denominator != 0){
        return numerator / denominator;
    }
    else{
        return 0;
    }
}

그리고, 위와 같이 NDEBUG를 파일 내에 직접 정의해서, 평가 여부를 조정할 수도 있지만, 일반적으로는 프로젝트의 전처리기 지시자를 설정하는 부분에 이를 추가하는 방식으로 사용합니다.

 

 

상수 표현식을 평가할 수 있는 static_assert

static_assert는 위의 assert와 달리, 상수 표현식( const expression )을 참 / 거짓을 검사할 수 있는 키워드입니다.

 

[C++] 컴파일 시 값을 알 수 있는 상수 표현식( const expression )

컴파일 시, 값을 알 수 있는 표현식( expression )표현식이란 리터럴( literal ), 변수( variable ), 연산자( operator ) 그리고 함수 호출( function call )의 연속된 식이라고 할 수 있습니다.#include int func( int a, in

codingbonfire.tistory.com

 

즉, 컴파일 시 static_assert 표현식을 평가하여, assert 매크로와 같은 기능을 수행할 수 있다는 것입니다.

만약, 상수 표현식이 거짓으로 밝혀지면, 이 표현식과 이해를 돕기 위한 메시지를 출력하고, 컴파일을 중단하게 됩니다.

 

그리고, 이 static_assert는 언어 자체가 지원하는 키워드로 지정되었기 때문에, assert 전처리기 매크로처럼 다른 파일을 포함할 필요가 없습니다. ( assert를 사용하려면 "cassert" 헤더 파일을 포함해야 합니다. )

게다가, assert처럼 반드시 함수 내에서만 사용해야 한다는 제약도 없습니다.

static_assert( sizeof(unsigned int) == 4 );	// 함수 외부에서도 사용 가능

int main(){

    int bitNum = sizeof(unsigned int) * CHAR_BIT;
    for( int i = 0; i < bitNum; i++){
        // do something
    }    
}

 

static_assert 구문은 다음과 같이 사용할 수 있습니다.

static_assert( 상수 표현식, 검사 실패 시 출력할 메시지 );

여기서, 표현식이 거짓일 때 출력할 메시지는, 위의 예처럼 생략할 수 있습니다.

 

이러한 static_assert컴파일 시 값을 알 수 있는 상수 표현식을 평가하므로, 프로그램 성능에 영향을 끼치지 않습니다.

#include <iostream>
#include <cstdint>  // for int64_t
#include <cassert>  // for assert

template <typename T>
int64_t getInt64( const T& var){
    
    static_assert( std::is_integral<T>::value, "type must be integral");    // ok
    
    assert(std::is_integral<T>::value && "type must be integral");  // ok
    
    return static_cast< int64_t >(var);
}

int main(){

    int a = 5;
    int64_t a64 = getInt64(a);
    std::cout << a64 << '\n';
}

하지만, 위의 assert 매크로의 표현식은 위 줄과 같은 평가를 수행하는데도, 이에 대한 비용을 지불해야 합니다.

그러므로, 상수 표현식의 참 / 거짓을 평가하기 위해선 static_assert를 사용하는 것이 타당합니다.

 

하지만, 만약 실행 시의 표현식을 검사하기 위해선 assert 매크로를 사용해야 합니다.

int main(){

    int x;
    std::cin >> x;

    assert( x > 0 && "x must be positive"); // ok
    static_assert( x > 0, "x must be positive");    // error !!
}

▼출력

error: non-constant condition for static assertion

 

그리고, assert 매크로는 프로그램이 이 문장을 실행하지 않으면, 표현식을 검사할 수 없습니다.

하지만, static_assert 표현식은 컴파일 시 항상 평가되어 그 결과를 알 수 있습니다.

int main(){
    
    if ( true){
        // 항상 실행되는 부분
    }
    else{
        assert(false);  // 평가되지 않습니다.

    	// 컴파일 시 항상 평가
        static_assert(false, "this expression will evaluate");
    }
}

▼출력

error: static assertion failed: this expression will evaluate

 

 

정리

  • 상수 표현식( const expression )의 참 / 거짓을 평가하고 싶다면, assert 전처리기 매크로 대신, static_assert 키워드를 사용하는 것이 유리합니다.

 

 

 

 

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유