GCCに特に重点を置いて、C(C++ではなく)でコンパイル時の静的アサートを実現する最良の方法は何ですか?
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でも実装されていることに注意してください。
これは、関数スコープおよび非関数スコープで機能します(ただし、構造体、ユニオン内では機能しません)。
_#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]
としても使用できます。これは、さびた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
_
私は質問が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」の幅がゼロ
From Wikipedia :
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}
COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
__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]
[〜#〜] 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
でのみ機能することに注意してください。変数に割り当てられた値など、メモリ内の値に基づく条件では動作しません(つまり、コンパイルが失敗します)。
古典的な方法は、配列を使用することです:
char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1];
アサーションがtrueの場合、配列のサイズは1で有効ですが、falseの場合、サイズ-1によりコンパイルエラーが発生するため機能します。
ほとんどのコンパイラは、変数の名前を表示し、アサーションに関する最終的なコメントを残すことができるコードの適切な部分を指し示します。
_Static_assert()
は、Cのすべてのバージョンのgccで定義されるようになりました。static_assert()
はC++ 11以降で定義されていますSTATIC_ASSERT()
の単純なマクロは以下で動作しますg++ -std=c++11
_)以降gcc -std=c90
_gcc -std=c99
_gcc -std=c11
_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;
}
_
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)
に展開されるマクロです。
これはいくつかの古い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
これは、「未使用の削除」オプションを設定して機能します。グローバルパラメータをチェックするために、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
//
本当に基本的でポータブルなものを望んでいるが、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'12 C++で私のために働いた