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でも実装されていることに注意してください。
error: expected declaration specifiers or '...' before 'sizeof'
はラインを取得しているのでstatic_assert( sizeof(int) == sizeof(long int), "Error!);
(ちなみに私はC ++ではなくCを使用しています)
_Static_assert( sizeof(int) == sizeof(long int), "Error!");
私のマシンでエラーが発生します。
error: expected declaration specifiers or '...' before 'sizeof'
AND error: expected declaration specifiers or '...' before string constant
(彼は"Error!"
文字列を参照しています)(また:私は-std = c11でコンパイルしています。宣言を関数内に置くとすべてうまくいきます(期待どおりに失敗して成功します))
_Static_assert
、C ++ ishではなくC標準を使用しましたstatic_assert
。static_assertマクロを取得するには、 `#include <assert.h>を実行する必要があります。
これは、関数スコープと非関数スコープで機能します(ただし、構造体、共用体の内部では機能しません)。
#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
-Og
ただし、これが機能するには、最低限の最適化レベル()で十分な場合が多く、デバッグを妨げることはありません。__OPTIMIZE__
(および__GNUC__
)が定義されていない場合は、静的アサーションをno-opまたはランタイムアサーションにすることを検討できます。
__LINE__
はgcc4.1.1のバージョンに似たものを使用しています... 2つの異なるヘッダーが同じ番号の行に1つあると、ときどき煩わしくなります!
質問で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'
ウィキペディアから:
#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
、コンパイル時に評価されるものでのみ機能することに注意してください。変数に割り当てられた値など、メモリ内の値に基づく条件では機能しません(つまり、コンパイルが失敗します)。
と一緒に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]
古典的な方法は、配列を使用することです。
char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1];
アサーションがtrueの場合、配列のサイズは1で有効であるため機能しますが、falseの場合、サイズが-1の場合はコンパイルエラーが発生します。
ほとんどのコンパイラは変数の名前を表示し、アサーションに関する最終的なコメントを残すことができるコードの右側の部分を指します。
#define STATIC_ASSERT()
型マクロにまとめ、よりジェネリックな例とを使用したジェネリックな例からのサンプルコンパイラ出力を提供すると、よりSTATIC_ASSERT()
多くの賛成票が得られ、この手法がより理にかなっていると思います。
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)
に展開されるマクロです。
_Static_assert()
Cのすべてのバージョンに対してgccで定義されるようになりました。 static_assert()
C ++ 11以降で定義されていますSTATIC_ASSERT()
機能します。g++ -std=c++11
)以降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 '
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'
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()'
static_assert.c:74:32:エラー:静的アサーションに失敗しました:(1> 2)失敗しました
#define _Static_assert static_assert / *static_assert
はC ++ 11以降の一部です* /
^
static_assert.c:78:38:注:マクロ '_Static_assert'の展開中
#defineSTATIC_ASSERT(test_for_true)_Static_assert((test_for_true)、 "(" #test_for_true ")failed")
^
static_assert.c:88:5:注:マクロの展開中 'STATIC_ASSERT'
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()'
static_assert.c:78:99:エラー: 'static_assert'はこのスコープで宣言されていません
#defineSTATIC_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. /programming/987684/does-gcc-have-a-built-in-compile-time-assert/987756#987756
2. /programming/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;
}
static_assert
マクロがあるのに、なぜそんなに複雑なのassert.h
ですか?
static_assert()
Cではまったく使用できません。en.cppreference.com / w / cpp / language / static_assertも参照してください。static_assert
「(C ++ 11以降)」が存在することを示しています。私の答えの美しさは、GccのC90以降、およびC ++ 11以降だけでなく、C ++ 11以降でも機能することstatic_assert()
です。また、私の答えは何が複雑ですか?それはほんの数#define
秒です。
static_assert
C11以降Cで定義されています。に展開されるマクロです_Static_assert
。en.cppreference.com/w/c/error/static_assert。さらに、あなたの答え_Static_assert
とは対照的に、gccのc99とc90では利用できません(gnu99とgnu90でのみ)。これは規格に準拠しています。基本的に、多くの余分な作業を行います。これは、gnu90およびgnu99でコンパイルした場合にのみメリットがあり、実際のユースケースはわずかに小さくなります。
本当に基本的でポータブルなものが欲しいが、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
_Static_assert
。