2009-04-30 4 views
31

나는 런타임에 오류가 발생하지 않고 컴파일을 방해하는 "assert"를 구현하려고한다.C 컴파일러가 어설 션을 구현하는 방법?

나는 현재 이와 같이 정의 된 바있는 훌륭한 바이너리 파일이지만 크기가 커집니다.

#define MY_COMPILER_ASSERT(EXPRESSION) switch (0) {case 0: case (EXPRESSION):;} 

샘플 코드 (컴파일에 실패 함).

#define DEFINE_A 1 
#define DEFINE_B 1 
MY_COMPILER_ASSERT(DEFINE_A == DEFINE_B); 

생성 된 바이너리의 크기를 최소화하기 위해 코드를 생성하지 못하도록 어떻게 구현할 수 있습니까?

+0

나는 C로 정적 어설 션을 생성하는 것이 가능하다고 생각하지 않는다. –

+0

몇 가지 좋은 답변과 중복 : http://stackoverflow.com/questions/174356/ways-to-assert-expressions-at-build-time-in-c –

+3

이 질문은 상대적으로 오래되었으므로 : _Static_assert'와 관련 'static_assert' 매크로는 C11에서 표준화되었습니다. 이것은 현재 언어에 내장되어 있습니다. – Leushenko

답변

35

순수 표준 C에서 컴파일 타임 어설 션이 가능하며 약간의 전처리 기교가 사용 시간을 assert()처럼 깨끗하게 표시합니다.

핵심 요령은 컴파일 타임에 평가할 수 있고 일부 값에 오류가 발생할 수있는 구문을 찾는 것입니다. 하나의 대답은 배열의 선언이 음수 크기를 가질 수 없다는 것입니다. typedef를 사용하면 성공시 공간 할당을 방지하고 실패시 오류를 보존합니다.

오류 메시지 자체는 암호문이 음수 인 선언 (GCC는 "배열의 크기가 음수 임)"을 암시 적으로 나타낼 것이므로이 오류가 실제로 어설 션 검사임을 암시하는 배열 유형의 이름을 선택해야합니다 .

더 많은 문제는 컴파일 단위에서 한 번만 typedef 특정 유형 이름이 가능하다는 것입니다. 따라서 매크로는 선언 할 고유 한 형식 이름을 얻기 위해 각 용도별로 정렬해야합니다.

내 일반적인 해결책은 매크로에 두 개의 매개 변수가 필요하다는 것입니다. 첫 번째는 어설 션 조건이 true이고 두 번째 조건이 장면 뒤에 선언 된 유형 이름의 일부입니다. 플 린스에 의한 대답은 토큰 붙여 넣기와 여분의 인수가 필요없는 고유 한 이름을 형성하기 위해 미리 정의 된 매크로를 사용함을 암시합니다.

불행히도, 어설 션 확인이 포함 된 파일에 있으면 두 번째 포함 파일의 동일한 줄 번호 나 주 원본 파일의 해당 줄 번호와 충돌 할 수 있습니다. 매크로 __FILE__을 사용하여이를 처리 할 수 ​​있지만 문자열 상수로 정의되며 문자열 상수를 식별자 이름의 일부로 되돌릴 수있는 전 처리기 트릭이 없습니다. 합법적 인 파일 이름에는 식별자의 합법적 인 부분이 아닌 문자가 포함될 수 있습니다. 일반적인 사용은 같은 수 있습니다

/** A compile time assertion check. 
* 
* Validate at compile time that the predicate is true without 
* generating code. This can be used at any point in a source file 
* where typedef is legal. 
* 
* On success, compilation proceeds normally. 
* 
* On failure, attempts to typedef an array type of negative size. The 
* offending line will look like 
*  typedef assertion_failed_file_h_42[-1] 
* where file is the content of the second parameter which should 
* typically be related in some obvious way to the containing file 
* name, 42 is the line number in the file on which the assertion 
* appears, and -1 is the result of a calculation based on the 
* predicate failing. 
* 
* \param predicate The predicate to test. It must evaluate to 
* something that can be coerced to a normal C boolean. 
* 
* \param file A sequence of legal identifier characters that should 
* uniquely identify the source file in which this condition appears. 
*/ 
#define CASSERT(predicate, file) _impl_CASSERT_LINE(predicate,__LINE__,file) 

#define _impl_PASTE(a,b) a##b 
#define _impl_CASSERT_LINE(predicate, line, file) \ 
    typedef char _impl_PASTE(assertion_failed_##file##_,line)[2*!!(predicate)-1]; 

:

그래서, 다음의 코드 제안 할

 
$ gcc -c demo.c 
demo.c:32: error: size of array `assertion_failed_demo_c_32' is negative 
$ 
: GCC에서

#include "CAssert.h" 
... 
struct foo { 
    ... /* 76 bytes of members */ 
}; 
CASSERT(sizeof(struct foo) == 76, demo_c); 

을 어설 션 오류는 같을 것이다

+1

이식성에 신경 쓰지 않는다면 GCC \ _ \ _ COUNTER \ _ \ _를 사용하여 typedef 이름에 붙여 넣기위한 고유 한 식별자를 제공 할 수 있습니다. 그것은 최근에 추가되었습니다 (4.3) –

+0

저는 C 전처리 기가 __COUNTER__와 같은 것을 갖지 못했음에 놀랐습니다. 매크로 어셈블러에는 매크로 어셈블러가있는 한 비슷한 구조가 있습니다. 한 매크로가 필요로하는 고유 한 심볼을 작성하는 데 사용할 수있는 매크로를 정의 할 수있는 것은 말할 필요도 없습니다. 불행히도 제 임베디드 시스템 프로젝트는 일반적으로 gcc 3.4.5 정도가됩니다. C89를 (거의) 준수하는 일부 독점 컴파일러가 아니라면 말이죠. – RBerteig

+0

@RBerteig 안녕하세요, 저는이 코드를 오픈 소스 프로젝트에 사용하고 싶습니다. 라이센스를 어떻게 받겠습니까? –

2

최종 바이너리를 컴파일 할 때 출력이 결과에 포함되지 않도록 MY_COMPILER_ASSERT를 공백으로 정의하십시오. 디버깅을위한 방법으로 만 정의하십시오.

하지만 정말로, 당신은 모든 주장을 이런 식으로 알 수 없을 것입니다. 어떤 사람들은 컴파일 타임에 (값이 null이 아니라는 주장과 같이) 이해하지 못한다. 당신이 할 수있는 것은 다른 #defines의 값을 검증하는 것입니다. 왜 그렇게하고 싶지는 모르겠다.

+4

컴파일 타임에 확인할 수있는 모든 것이 런타임에 테스트 케이스를 필요로하지 않기 때문에 유용합니다. 휴대용 프로토콜 구현을 구현할 때 구조 크기 및 레이아웃에 대한 가정을 검증하는 것이 유용합니다. 크기와 오프셋은 컴파일 타임에 알려지기 때문에 크기와 오프셋을 테스트하는 것이 좋습니다. 또한 코드 생성없이 컴파일 타임 어설 션을 구현한다는 것은 릴리즈 빌드에서 제거 할 이유가 없다는 것을 의미합니다. 최악의 경우에는 고아 유형의 이름을 가진 기호 테이블이 혼란 스럽습니다. – RBerteig

3

컴파일러가 DEBUG 또는 NDEBUG 같은 전 처리기 매크로를 설정하면이 같은 (그렇지 않으면 당신은 메이크 파일이를 설정할 수 있습니다) 만들 수 있습니다

#ifdef DEBUG 
#define MY_COMPILER_ASSERT(EXPRESSION) switch (0) {case 0: case (EXPRESSION):;} 
#else 
#define MY_COMPILER_ASSERT(EXPRESSION) 
#endif 

그런 다음, 컴파일러는 디버그 빌드에 대해서만 주장합니다.

1

'#error'는 대부분의 컴파일러에서 컴파일을 중지시키는 유효한 선행 프로세서 정의입니다. 당신은 디버그 컴파일을 방지하기 위해, 예를 들어, 다음과 같이 그것을 할 수 있습니다 : 나는 C에서 정적 주장에서 찾을 수


#ifdef DEBUG 
#error Please don't compile now 
#endif 
+4

불행히도 이것은 전처리 수준에서 중단되므로'assert (sizeof (long) == sizeof (void *)) '와 같은 것을 처리 할 수 ​​없습니다. – ephemient

4

가장 좋은 작성자는 pixelbeat이다. C++ 0X에 정적 어설 션이 추가되어 C1X로 돌아갈 수는 있지만 잠시 동안은 그렇지 않습니다. 내가 준 링크의 매크로가 바이너리의 크기를 늘리는 지 모르겠습니다. 합리적인 수준의 최적화로 컴파일한다면 적어도 그렇지 않을 거라고 생각하지만 마일리지는 다를 수 있습니다.

-1

글쎄, static asserts in the boost library을 사용할 수 있습니다.

내가 믿는 것은 배열을 정의하는 것입니다. EXPRESSION가 true의 경우

#define MY_COMPILER_ASSERT(EXPRESSION) char x[(EXPRESSION)]; 

, 그것은 OK 인 char x[1];을 정의합니다. false이면 char x[0];으로 불법입니다.

+0

하지만 C89에서는 변수가 범위의 맨 위에서 만 선언 될 수 있기 때문에 컴파일이 중단됩니다. 아마 중괄호로 묶는다면 그것을 고칠 수 있을까요? 또한 크기가 0 인 배열은 ISO C에서 금지되어 있습니다. 또한 자체 선언 범위가 아닌 경우 여러 선언 및 사용되지 않는 변수에 대한 경고를 받게됩니다. – dreamlax

+1

'#define MY_COMPILER_ASSERT (EXPRESSION) do {char x [(EXPRESSION)? 1 : -1];} while (0)'이 더 좋을 것입니다 : 중괄호 안의 선언은 합법적이고 범위가 지정됩니다. 모든 C 버전에서 유효/무효이며 C 언어의 다른 모든 함수와 시각적 인 일관성을 위해'MY_COMPILER_ASSERT (...); '를 후행 세미콜론으로 강제합니다. – ephemient

+0

사용되지 않은 변수를 파기하는 것이 typedef를 선언하는 것보다 낫습니다 . 사용되지 않는 typedef는 무해하지만 사용되지 않는 변수는 많은 컴파일러에서 경고를 생성 할 수 있습니다. –

3

당신이 C에 관심이 있다는 것을 알고 있지만 부스트의 C++ static_assert을 살펴보십시오. (덧붙여,이 가능성이 C++ 1X로 제공되고있다.)

우리는 C++ 다시 비슷한 짓을했습니다

 
#define COMPILER_ASSERT(expr) enum { ARG_JOIN(CompilerAssertAtLine, __LINE__) = sizeof(char[(expr) ? +1 : -1]) } 

이 분명히에만 C++에서 작동합니다. This article은 C에서 사용하기 위해 그것을 수정하는 방법에 대해 설명합니다.

7

다음 COMPILER_VERIFY(exp) 매크로는 상당히 잘 작동합니다.

 
// combine arguments (after expanding arguments) 
#define GLUE(a,b) __GLUE(a,b) 
#define __GLUE(a,b) a ## b 

#define CVERIFY(expr, msg) typedef char GLUE (compiler_verify_, msg) [(expr) ? (+1) : (-1)] 

#define COMPILER_VERIFY(exp) CVERIFY (exp, __LINE__) 

C 및 C++ 모두에서 작동하며 typedef가 허용되는 곳이면 어디에서나 사용할 수 있습니다. 표현식이 참이면 1 char의 배열에 대한 typedef를 생성합니다 (무해합니다). 표현식이 거짓이면 -1 문자의 배열에 대한 typedef를 생성하므로 일반적으로 오류 메시지가 표시됩니다. arugment로 주어진 표현식은 컴파일 타임 상수로 평가되는 모든 것이 될 수 있습니다. 따라서 sizeof()가 포함 된 표현식이 잘 작동합니다. 이렇게하면 전처리 기가 평가할 수있는 표현식으로 제한되어있는

 
#if (expr) 
#error 
#endif 

보다 훨씬 유연 해집니다.

2

Leander가 말했듯이 정적 어설 션이 C++ 11에 추가되었습니다.

static_assert(exp, message)

예를

#include "myfile.hpp" 

static_assert(sizeof(MyClass) == 16, "MyClass is not 16 bytes!") 

void doStuff(MyClass object) { } 

를 들어 그것에 cppreference page을 참조하십시오.

+2

질문은 C가 아니라 C++입니다. –

관련 문제