실행 시 가정의 참/거짓을 검사하는 assert
다음은 두 실수를 전달받아서 나눈 결과를 반환하는 함수입니다.
double divide( double numerator, double denominator){
if ( denominator != 0){
return numerator / denominator;
}
else{
return 0; // denominator가 0이 될 때
}
}
우선, 이 함수는 의도한 대로 제대로 동작합니다.
하지만, 이것을 작성한 프로그래머는 ( 그전에 조치를 취해두었기 때문에 ) 나눗셈의 분모인 denominator
는 절대 0
이 될 수 없다고 생각할 수도 있습니다.
그래서, 만일 denominator
가 0
이 된다면, 어떤 경우에 이런 상황이 되는지 알고 싶을 것입니다.
이때, 사용할 수 있는 것이 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
위에서, denominator
가 0
이 되는 경우, 이 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 키워드를 사용하는 것이 유리합니다.