GCCに特に重点を置いて、C(C ++ではなく)でコンパイル時の静的アサーションを実現するための最良の方法は何ですか?
13 に答える
_Static_assert
C11標準はキーワードを追加します。
これはgcc-4.6以降に実装されています:
_Static_assert (0, "assert1"); /* { dg-error "static assertion failed: \"assert1\"" } */
最初のスロットは積分定数式である必要があります。2番目のスロットは、長くなる可能性のある定数文字列リテラルです(_Static_assert(0, L"assertion of doom!")
)。
これは最近のバージョンのclangでも実装されていることに注意してください。
これは、関数スコープと非関数スコープで機能します(ただし、構造体、共用体の内部では機能しません)。
#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);
}
コンパイル時のアサーションが一致しなかった場合、GCCによってほぼ理解できるメッセージが生成されます
sas.c:4: error: size of array ‘static_assertion_this_should_be_true’ is negative
マクロを変更して、typedefの一意の名前を生成することができます(つまり
__LINE__
、名前の最後に連結する必要がありますstatic_assert_...
) 。三元の代わりに、これも使用できます。これは
#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1]
、さびた古いcc65(6502 cpu用)コンパイラでも機能します。
更新:
完全を期すために、これが__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);
}
UPDATE2: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
cl
質問でgccが明示的に言及されていることは知っていますが、完全を期すために、ここではMicrosoftコンパイラーの微調整を行います。
負のサイズの配列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' </ p>
なぜなら:
_Static_assert()
Cのすべてのバージョンに対してgccで定義されるようになりました。static_assert()
C ++ 11以降で定義されています(C11でも、便利なマクロとして_Static_assert()
;こことここを参照してください)
したがって、次の単純なマクロは次の場所でSTATIC_ASSERT()
機能します。
- C ++:
- C ++ 11(
g++ -std=c++11
)以降
- C ++ 11(
- C:
gcc -std=c90
gcc -std=c99
gcc -std=c11
gcc
(標準は指定されていません)
次のように定義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()
コードは機能しますが、条件がfalseであり、コンパイル時のアサートが発生します):
$ gcc -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c:In function'main' <br /> static_assert.c:78:38:error:static assertion failed: "(1> 2)failed"
# define STATIC_ASSERT(test_for_true)_Static_assert((test_for_true)、 "(" #test_for_true ")failed")
^
static_assert.c:88:5:注:マクロの展開中'STATIC_ASSERT' <br /> STATIC_ASSERT(1> 2);
^
例2:良好なg++ -std=c++11
出力(つまり、STATIC_ASSERT()
コードは機能しますが、条件がfalseであり、コンパイル時のアサートが発生します):
$ g ++ -Wall -std = c ++ 11 -o static_assert static_assert.c && ./static_assert
static_assert.c:関数'int main()' <br /> static_assert.c:74:32:エラー:静的アサーションに失敗しました:(1> 2)失敗
#define _Static_assert static_assert/*static_assert
はC++11以降の一部です*/
^
static_assert.c:78:38:注:マクロ'_Static_assert' <br /> #define STATIC_ASSERT( test_for_true)_Static_assert((test_for_true)、 "(" #test_for_true ")failed")
^
static_assert.c:88:5:注:マクロの展開中'STATIC_ASSERT' <br /> STATIC_ASSERT(1> 2);
^
例3: 失敗したC ++出力(つまり、C ++11より前のバージョンのC++を使用しているため、アサートコードがまったく正しく機能しません):
$ 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()' <br /> static_assert.c:78:99:エラー:'static_assert'はこのスコープで宣言されていません
#defineSTATIC_ASSERT(test_for_true)_Static_assert((test_for_true)、 "( "#test_for_true")failed ")
^
static_assert.c:88:5:注:マクロ'STATIC_ASSERT'の展開中<br/>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;
}
関連している:
ウィキペディアから:
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}
COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
:を使用したソリューションの使用はお勧めしません。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
、コンパイル時に評価されるものでのみ機能することに注意してください。変数に割り当てられた値など、メモリ内の値に基づく条件では機能しません(つまり、コンパイルが失敗します)。
古典的な方法は、配列を使用することです。
char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1];
アサーションがtrueの場合、配列のサイズは1で有効であるため機能しますが、falseの場合、サイズ-1の場合はコンパイルエラーが発生します。
ほとんどのコンパイラは、変数の名前を表示し、アサーションに関する最終的なコメントを残すことができるコードの右側を指します。
STATIC_ASSERT()マクロを。とともに使用する場合__LINE__
、.cファイルのエントリとヘッダーファイルの別のエントリの間で行番号の衝突を回避するには、 。をインクルードします__INCLUDE_LEVEL__
。
例えば :
/* 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]
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>
)、それが使用されます。それ以外の場合、条件がfalseの場合、負のサイズのビットフィールドが宣言され、コンパイルが失敗します。
STMT_START
/は、それぞれ/にSTMT_END
展開されるマクロです。do
while (0)
本当に基本的でポータブルなものが欲しいが、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;
}
説明:
最初に、実際のアサートがあるかどうかをチェックします。これが使用可能な場合は、必ず使用する必要があります。
そうでない場合は、pred
icateを取得し、それをそれ自体で分割することによって主張します。これは2つのことを行います。
ゼロ、id estの場合、アサーションは失敗し、ゼロ除算エラーが発生します(配列を宣言しようとしているため、算術演算が強制されます)。
ゼロでない場合は、配列サイズをに正規化し1
ます。したがって、アサーションが合格した場合、述語が-1
(無効)または232442
(最適化されている場合はIDKの大量のスペースの浪費)と評価されたため、とにかく失敗したくないでしょう。中かっこで囲まれているため、これは変数をスコープするブロックになり
ますSTATIC_ASSERT
assert
、つまり何度でも書くことができます。
また、これをにキャストします。これは、警告void
を取り除くための既知の方法です。
の場合、コードブロック内にある代わりに、名前空間を生成します。関数の外部では名前空間を使用できます。これを複数回使用する場合は、競合する定義を停止するために識別子が必要です。
unused variable
GLOBAL_STATIC_ASSERT
unique
GCCとVS'12C++で私のために働いた
これは、「未使用の削除」オプションが設定されている場合に機能します。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
//
これはいくつかの古い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
C11より古いCバージョンの場合、独自の静的アサーションを作成することができます。以下は、古いバージョンのGCCでテストされています。
もちろん、C11を使用できる場合は、を使用するのが最も理にかなってい#include <assert.h>
ますstatic_assert
。
/** @file
* STATIC_ASSERT allows you to do compile time assertions at file scope or in a function.
* @param expr: a boolean expression that is valid at compile time.
* @param msg: a "message" that must also be a valid identifier, i.e. message_with_underscores
*/
#ifdef __GNUC__
#define STATIC_ASSERT_HELPER(expr, msg) \
(!!sizeof(struct { unsigned int STATIC_ASSERTION__##msg: (expr) ? 1 : -1; }))
#define STATIC_ASSERT(expr, msg) \
extern int (*assert_function__(void)) [STATIC_ASSERT_HELPER(expr, msg)]
#else
#define STATIC_ASSERT(expr, msg) \
extern char STATIC_ASSERTION__##msg[1]; \
extern char STATIC_ASSERTION__##msg[(expr)?1:2]
#endif /* #ifdef __GNUC__ */
#define STATIC_ASSERT_ARRAY_LEN(array, len) \
STATIC_ASSERT(sizeof(array)/sizeof(array[0]) == len, array##_wrong_size);
#endif // STATIC_ASSERT_H
配列ヘルパーとgnucの特殊なケースがあることを除けば、考え方は基本的にハッシュブラウンの回答と同じです。