web-dev-qa-db-ja.com

Cの静的アサート

GCCに特に重点を置いて、C(C++ではなく)でコンパイル時の静的アサートを実現する最良の方法は何ですか?

75
Matt Joiner

C11標準では、__Static_assert_キーワードが追加されています。

これは gcc-4.6以降に実装

__Static_assert (0, "assert1"); /* { dg-error "static assertion failed: \"assert1\"" } */
_

最初のスロットは、整数定数式である必要があります。 2番目のスロットは、長い可能性のある定数文字列リテラルです(_Static_assert(0, L"assertion of Doom!"))。

これは最近のバージョンのclangでも実装されていることに注意してください。

77
emsr

これは、関数スコープおよび非関数スコープで機能します(ただし、構造体、ユニオン内では機能しません)。

_#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]

STATIC_ASSERT(1,this_should_be_true); 

int main()
{
 STATIC_ASSERT(1,this_should_be_true); 
}
_
  1. コンパイル時のアサーションが一致しなかった場合、GCC _sas.c:4: error: size of array ‘static_assertion_this_should_be_true’ is negative_によってほとんどわかりやすいメッセージが生成されます

  2. マクロは、typedefの一意の名前を生成するように変更する必要があります(つまり、___LINE___名の最後に_static_assert_..._を連結します)

  3. 三値の代わりに、これは#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1]としても使用できます。これは、さびたolde cc65(6502 cpu用)コンパイラでも動作することがあります。

PDATE:完全を期すために、これは___LINE___のバージョンです

_#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(!!(COND))*2-1]
// token pasting madness:
#define COMPILE_TIME_ASSERT3(X,L) STATIC_ASSERT(X,static_assertion_at_line_##L)
#define COMPILE_TIME_ASSERT2(X,L) COMPILE_TIME_ASSERT3(X,L)
#define COMPILE_TIME_ASSERT(X)    COMPILE_TIME_ASSERT2(X,__LINE__)

COMPILE_TIME_ASSERT(sizeof(long)==8); 
int main()
{
    COMPILE_TIME_ASSERT(sizeof(int)==4); 
}
_

PDATE2:GCC固有のコード

GCC 4.3(私は推測する)は、「エラー」および「警告」機能属性を導入しました。その属性を持つ関数の呼び出しをデッドコードの除去(または他の手段)で除去できなかった場合、エラーまたは警告が生成されます。これを使用して、ユーザー定義の障害記述でコンパイル時のアサートを行うことができます。ダミー関数に頼らずに名前空間スコープでそれらをどのように使用できるかを決定することは残っています。

_#define CTC(X) ({ extern int __attribute__((error("assertion failure: '" #X "' not true"))) compile_time_check(); ((X)?0:compile_time_check()),0; })

// never to be called.    
static void my_constraints()
{
CTC(sizeof(long)==8); 
CTC(sizeof(int)==4); 
}

int main()
{
}
_

そして、これはどのように見えるかです:

_$ gcc-mp-4.5 -m32 sas.c 
sas.c: In function 'myc':
sas.c:7:1: error: call to 'compile_time_check' declared with attribute error: assertion failure: `sizeof(int)==4` not true
_
81

cl

私は質問がgccに明示的に言及していることを知っていますが、ここでは完全を期すためにMicrosoftコンパイラーのTweakを紹介します。

負のサイズの配列typedefを使用してもclが適切なエラーを吐き出すことはありません。 _error C2118: negative subscript_とだけ書かれています。この点では、幅がゼロのビットフィールドの方がうまくいきます。これには構造体の型定義が含まれるため、実際には一意の型名を使用する必要があります。 ___LINE___はマスタードをカットしません—ヘッダーとソースファイルの同じ行にCOMPILE_TIME_ASSERT()を含めることができ、コンパイルが失敗します。 ___COUNTER___が助けになります(4.3からgccにあります)。

_#define CTASTR2(pre,post) pre ## post
#define CTASTR(pre,post) CTASTR2(pre,post)
#define STATIC_ASSERT(cond,msg) \
    typedef struct { int CTASTR(static_assertion_failed_,msg) : !!(cond); } \
        CTASTR(static_assertion_failed_,__COUNTER__)
_

いま

_STATIC_ASSERT(sizeof(long)==7, use_another_compiler_luke)
_

clの下にあるもの:

エラーC2149: 'static_assertion_failed_use_another_compiler_luke':名前付きビットフィールドの幅をゼロにすることはできません

Gccはわかりやすいメッセージも提供します。

エラー:ビットフィールド「static_assertion_failed_use_another_compiler_luke」の幅がゼロ

11
bobbogo

From Wikipedia

#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}

COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
4
Tyler

__LINE__でSTATIC_ASSERT()マクロを使用する場合、__INCLUDE_LEVEL__を含めることにより、.cファイルのエントリとヘッダーファイルの別のエントリとの間の行番号の衝突を回避できます。

例えば ​​:

/* Trickery to create a unique variable name */
#define BOOST_JOIN( X, Y )      BOOST_DO_JOIN( X, Y )
#define BOOST_DO_JOIN( X, Y )   BOOST_DO_JOIN2( X, Y )
#define BOOST_DO_JOIN2( X, Y )  X##Y
#define STATIC_ASSERT(x)        typedef char \
        BOOST_JOIN( BOOST_JOIN(level_,__INCLUDE_LEVEL__), \
                    BOOST_JOIN(_assert_on_line_,__LINE__) ) [(x) ? 1 : -1]
2
BrentNZ

[〜#〜] not [〜#〜]typedefを使用したソリューションの使用を推奨します。

#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]

typedefキーワードを使用した配列宣言は、コンパイル時に評価されることが保証されていません。たとえば、ブロックスコープの次のコードはコンパイルされます。

int invalid_value = 0;
STATIC_ASSERT(invalid_value, this_should_fail_at_compile_time_but_will_not);

代わりにこれをお勧めします(C99):

#define STATIC_ASSERT(COND,MSG) static int static_assertion_##MSG[(COND)?1:-1]

staticキーワードのため、配列はコンパイル時に定義されます。このアサートは、コンパイル時に評価されるCONDでのみ機能することに注意してください。変数に割り当てられた値など、メモリ内の値に基づく条件では動作しません(つまり、コンパイルが失敗します)。

2

古典的な方法は、配列を使用することです:

char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1];

アサーションがtrueの場合、配列のサイズは1で有効ですが、falseの場合、サイズ-1によりコンパイルエラーが発生するため機能します。

ほとんどのコンパイラは、変数の名前を表示し、アサーションに関する最終的なコメントを残すことができるコードの適切な部分を指し示します。

1
Paolo.Bolzoni

なぜなら:

  1. _Static_assert()は、Cのすべてのバージョンのgccで定義されるようになりました。
  2. static_assert()はC++ 11以降で定義されています

したがって、次のSTATIC_ASSERT()の単純なマクロは以下で動作します

  1. C++:
    1. C++ 11(_g++ -std=c++11_)以降
  2. C:
    1. _gcc -std=c90_
    2. _gcc -std=c99_
    3. _gcc -std=c11_
    4. gcc(stdが指定されていない)

次のように_STATIC_ASSERT_を定義します:

_/* For C++: */
#ifdef __cplusplus
    #ifndef _Static_assert
        #define _Static_assert static_assert /* `static_assert` is part of C++11 or later */
    #endif
#endif
/* Now for gcc (C) (and C++, given the define above): */
#define STATIC_ASSERT(test_for_true) _Static_assert((test_for_true), "(" #test_for_true ") failed")
_

今すぐ使用:

_STATIC_ASSERT(1 > 2); // Output will look like: error: static assertion failed: "(1 > 2) failed" 
_

例:

Gcc 4.8.4を使用してUbuntuでテスト:

例1:良好なgcc出力(つまり、STATIC_ASSERT()コードは機能しますが、条件が偽であり、コンパイル時にアサートします):

$ gcc -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c:関数「main」内
static_assert.c:78:38:エラー:静的アサーションに失敗しました:「(1> 2)failed」
#define STATIC_ASSERT(test_for_true)_Static_assert((test_for_true)、 "(" #test_for_true ")が失敗しました")
^
static_assert.c:88:5:注:マクロ「STATIC_ASSERT」の展開中
STATIC_ASSERT(1> 2);
^

例2: good _g++ -std=c++11_出力(つまり:STATIC_ASSERT()コードは機能しますが、条件が偽であり、コンパイル時にアサートします):

$ g ++ -Wall -std = c ++ 11 -o static_assert static_assert.c && ./static_assert
static_assert.c:関数「int main()」内
static_assert.c:74:32:エラー:静的アサーションに失敗しました:(1> 2)failed
#define _Static_assert static_assert/* _static_assert_はC++ 11以降の一部です* /
^
static_assert.c:78:38:注:マクロ「_Static_assert」の展開中
#define STATIC_ASSERT(test_for_true)_Static_assert((test_for_true)、 "(" #test_for_true ")が失敗しました")
^
static_assert.c:88:5:注:マクロ「STATIC_ASSERT」の展開中
STATIC_ASSERT(1> 2);
^

例3:failedC++出力(つまり、アサートコードは、 C++のバージョンbeforeC++ 11):

$ g ++ -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c:88:5:警告:識別子「static_assert」はC++ 11のキーワードです[-Wc ++ 0x-compat]
STATIC_ASSERT(1> 2);
^
static_assert.c:関数「int main()」内
static_assert.c:78:99:エラー:「static_assert」はこのスコープで宣言されていません
#define STATIC_ASSERT(test_for_true)_Static_assert((test_for_true)、 "(" #test_for_true ")が失敗しました")
^
static_assert.c:88:5:注:マクロ「STATIC_ASSERT」の展開中
STATIC_ASSERT(1> 2);
^

完全なテスト結果はこちら:

_/*
static_assert.c
- test static asserts in C and C++ using gcc compiler

Gabriel Staples
4 Mar. 2019 

To be posted in:
1. https://stackoverflow.com/questions/987684/does-gcc-have-a-built-in-compile-time-assert/987756#987756
2. https://stackoverflow.com/questions/3385515/static-assert-in-c/7287341#7287341

To compile & run:
  C:
    gcc -Wall -o static_assert static_assert.c && ./static_assert
    gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert
    gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert
    gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert
  C++:
    g++ -Wall -o static_assert static_assert.c && ./static_assert
    g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert
    g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert
    g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert

-------------
TEST RESULTS:
-------------

1. `_Static_assert(false, "1. that was false");` works in:
  C:
    gcc -Wall -o static_assert static_assert.c && ./static_assert             YES
    gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert    YES
    gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert    YES
    gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert    YES
  C++:
    g++ -Wall -o static_assert static_assert.c && ./static_assert             NO
    g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert  NO
    g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert  NO
    g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert  NO

2. `static_assert(false, "2. that was false");` works in:
  C:
    gcc -Wall -o static_assert static_assert.c && ./static_assert             NO
    gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert    NO
    gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert    NO
    gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert    NO
  C++:
    g++ -Wall -o static_assert static_assert.c && ./static_assert             NO
    g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert  NO
    g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert  NO
    g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert  YES

3. `STATIC_ASSERT(1 > 2);` works in:
  C:
    gcc -Wall -o static_assert static_assert.c && ./static_assert             YES
    gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert    YES
    gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert    YES
    gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert    YES
  C++:
    g++ -Wall -o static_assert static_assert.c && ./static_assert             NO
    g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert  NO
    g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert  NO
    g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert  YES

*/

#include <stdio.h>
#include <stdbool.h>

/* For C++: */
#ifdef __cplusplus
    #ifndef _Static_assert
        #define _Static_assert static_assert /* `static_assert` is part of C++11 or later */
    #endif
#endif
/* Now for gcc (C) (and C++, given the define above): */
#define STATIC_ASSERT(test_for_true) _Static_assert((test_for_true), "(" #test_for_true ") failed")


int main(void)
{
    printf("Hello World\n");

    /*_Static_assert(false, "1. that was false");*/
    /*static_assert(false, "2. that was false");*/

    STATIC_ASSERT(1 > 2);

    return 0;
}
_
1
Gabriel Staples

Perlから、具体的には _Perl.h_行3455 (事前に_<assert.h>_が含まれています):

_/* STATIC_ASSERT_DECL/STATIC_ASSERT_STMT are like assert(), but for compile
   time invariants. That is, their argument must be a constant expression that
   can be verified by the compiler. This expression can contain anything that's
   known to the compiler, e.g. #define constants, enums, or sizeof (...). If
   the expression evaluates to 0, compilation fails.
   Because they generate no runtime code (i.e.  their use is "free"), they're
   always active, even under non-DEBUGGING builds.
   STATIC_ASSERT_DECL expands to a declaration and is suitable for use at
   file scope (outside of any function).
   STATIC_ASSERT_STMT expands to a statement and is suitable for use inside a
   function.
*/
#if (defined(static_assert) || (defined(__cplusplus) && __cplusplus >= 201103L)) && (!defined(__IBMC__) || __IBMC__ >= 1210)
/* static_assert is a macro defined in <assert.h> in C11 or a compiler
   builtin in C++11.  But IBM XL C V11 does not support _Static_assert, no
   matter what <assert.h> says.
*/
#  define STATIC_ASSERT_DECL(COND) static_assert(COND, #COND)
#else
/* We use a bit-field instead of an array because gcc accepts
   'typedef char x[n]' where n is not a compile-time constant.
   We want to enforce constantness.
*/
#  define STATIC_ASSERT_2(COND, SUFFIX) \
    typedef struct { \
        unsigned int _static_assertion_failed_##SUFFIX : (COND) ? 1 : -1; \
    } _static_assertion_failed_##SUFFIX Perl_UNUSED_DECL
#  define STATIC_ASSERT_1(COND, SUFFIX) STATIC_ASSERT_2(COND, SUFFIX)
#  define STATIC_ASSERT_DECL(COND)    STATIC_ASSERT_1(COND, __LINE__)
#endif
/* We need this wrapper even in C11 because 'case X: static_assert(...);' is an
   error (static_assert is a declaration, and only statements can have labels).
*/
#define STATIC_ASSERT_STMT(COND)      STMT_START { STATIC_ASSERT_DECL(COND); } STMT_END
_

_static_assert_が利用可能な場合(_<assert.h>_から)、それが使用されます。それ以外の場合、条件が偽の場合、負のサイズのビットフィールドが宣言され、コンパイルが失敗します。

_STMT_START_/_STMT_END_は、それぞれdo/while (0)に展開されるマクロです。

0
melpomene

これはいくつかの古いgccで機能しました。申し訳ありませんが、どのバージョンであったか忘れました:

#define _cat(x, y) x##y

#define _sassert(exp, ln)\
extern char _cat(SASSERT_, ln)[1]; \
extern char _cat(SASSERT_, ln)[exp ? 1 : 2]

#define sassert(exp) _sassert((exp), __LINE__)

//
sassert(1 == 2);

//
#148 declaration is incompatible with "char SASSERT_134[1]" (declared at line 134)  main.c  /test/source/controller line 134    C/C++ Problem
0
jay

これは、「未使用の削除」オプションを設定して機能します。グローバルパラメータをチェックするために、1つのグローバル関数を使用できます。

//
#ifndef __sassert_h__
#define __sassert_h__

#define _cat(x, y) x##y

#define _sassert(exp, ln) \
extern void _cat(ASSERT_WARNING_, ln)(void); \
if(!(exp)) \
{ \
    _cat(ASSERT_WARNING_, ln)(); \
}

#define sassert(exp) _sassert(exp, __LINE__)

#endif //__sassert_h__

//-----------------------------------------
static bool tab_req_set_relay(char *p_packet)
{
    sassert(TXB_TX_PKT_SIZE < 3000000);
    sassert(TXB_TX_PKT_SIZE >= 3000000);
    ...
}

//-----------------------------------------
Building target: ntank_app.elf
Invoking: Cross ARM C Linker
arm-none-eabi-gcc ...
../Sources/Host_if/tab_if.c:637: undefined reference to `ASSERT_WARNING_637'
collect2: error: ld returned 1 exit status
make: *** [ntank_app.elf] Error 1
//
0
user4978854

本当に基本的でポータブルなものを望んでいるが、C++ 11の機能にアクセスできない人のために、私はそのことだけを書きました。
通常STATIC_ASSERTを使用し(必要に応じて同じ関数で2回記述することができます)、関数の外側でGLOBAL_STATIC_ASSERTを使用し、一意のフレーズを最初のパラメーターとして使用します。

#if defined(static_assert)
#   define STATIC_ASSERT static_assert
#   define GLOBAL_STATIC_ASSERT(a, b, c) static_assert(b, c)
#else
#   define STATIC_ASSERT(pred, explanation); {char assert[1/(pred)];(void)assert;}
#   define GLOBAL_STATIC_ASSERT(unique, pred, explanation); namespace ASSERTATION {char unique[1/(pred)];}
#endif

GLOBAL_STATIC_ASSERT(first, 1, "Hi");
GLOBAL_STATIC_ASSERT(second, 1, "Hi");

int main(int c, char** v) {
    (void)c; (void)v;
    STATIC_ASSERT(1 > 0, "yo");
    STATIC_ASSERT(1 > 0, "yo");
//    STATIC_ASSERT(1 > 2, "yo"); //would compile until you uncomment this one
    return 0;
}

説明:
最初に、実際のアサートがあるかどうかをチェックします。これが使用可能であれば、間違いなく使用する必要があります。
そうしない場合は、predicateを取得し、それ自体で分割してアサートします。これは2つのことを行います。
ゼロ、id est、アサーションが失敗した場合、ゼロ除算エラーが発生します(配列を宣言しようとしているため、算術が強制されます)。
ゼロでない場合、配列サイズを1に正規化します。したがって、アサーションが合格した場合、述部が-1(無効)に評​​価されるか、232442(スペースの大量浪費、最適化された場合はIDK)に評価されるため、とにかく失敗することは望ましくありません。
STATIC_ASSERTの場合、これは中括弧で囲まれているため、ブロックになり、変数assertのスコープになります。つまり、何度でも記述できます。
また、voidにキャストします。これは、unused variable警告を取り除く既知の方法です。
_GLOBAL_STATIC_ASSERTの場合、コードブロックではなく、名前空間を生成します。名前空間は関数の外部で許可されます。これを複数回使用する場合、競合する定義を停止するにはunique識別子が必要です。


GCCとVS'12 C++で私のために働いた

0
Hashbrown