Apa cara terbaik untuk mencapai pernyataan statis waktu kompilasi di C (bukan C ++), dengan penekanan khusus pada GCC?
c
gcc
assert
compile-time
static-assert
Matt Joiner
sumber
sumber
_Static_assert
adalah bagian dari standar C11 dan semua kompilator yang mendukung C11, akan memilikinya.error: expected declaration specifiers or '...' before 'sizeof'
untuk jalurstatic_assert( sizeof(int) == sizeof(long int), "Error!);
(omong-omong saya menggunakan C bukan C ++)_Static_assert( sizeof(int) == sizeof(long int), "Error!");
Di macine saya, saya mendapatkan kesalahan.error: expected declaration specifiers or '...' before 'sizeof'
ANDerror: expected declaration specifiers or '...' before string constant
(dia mengacu pada"Error!"
string) (juga: Saya mengkompilasi dengan -std = c11. Saat meletakkan deklarasi di dalam fungsi semua bekerja dengan baik (gagal dan berhasil seperti yang diharapkan))_Static_assert
bukan C ++ ishstatic_assert
. Anda perlu `#include <assert.h> untuk mendapatkan makro static_assert.Ini berfungsi dalam lingkup fungsi dan non-fungsi (tetapi tidak di dalam struct, unions).
#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); }
Jika pernyataan waktu kompilasi tidak dapat dicocokkan, pesan yang hampir dapat dipahami akan dihasilkan oleh GCC
sas.c:4: error: size of array ‘static_assertion_this_should_be_true’ is negative
Makro bisa atau harus diubah untuk menghasilkan nama unik untuk typedef (yaitu gabungan
__LINE__
di akhirstatic_assert_...
nama)Alih-alih terner, ini juga bisa digunakan
#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1]
yang kebetulan bekerja bahkan pada compiler cc65 berkarat olde (untuk 6502 cpu).UPDATE: Demi kelengkapan, berikut versi dengan
__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: kode khusus GCC
GCC 4.3 (saya kira) memperkenalkan atribut fungsi "error" dan "peringatan". Jika panggilan ke fungsi dengan atribut itu tidak dapat dihilangkan melalui penghapusan kode mati (atau tindakan lain) maka kesalahan atau peringatan akan dihasilkan. Ini dapat digunakan untuk membuat waktu kompilasi menegaskan dengan deskripsi kegagalan yang ditentukan pengguna. Tetap menentukan bagaimana mereka dapat digunakan dalam cakupan namespace tanpa menggunakan fungsi dummy:
#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() { }
Dan seperti inilah tampilannya:
$ 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
sumber
-Og
) mungkin cukup untuk bekerja, namun, dan tidak boleh mengganggu proses debug. Seseorang dapat mempertimbangkan untuk membuat pernyataan statis sebagai pernyataan tanpa operasi atau runtime jika__OPTIMIZE__
(dan__GNUC__
) tidak ditentukan.__LINE__
versi di gcc 4.1.1 ... dengan gangguan sesekali ketika dua header berbeda kebetulan memiliki satu di baris nomor yang sama!cl
Saya tahu pertanyaan itu secara eksplisit menyebutkan gcc, tetapi hanya untuk kelengkapan di sini adalah tweak untuk kompiler Microsoft.
Menggunakan array berukuran negatif typedef tidak membujuk cl untuk mengeluarkan kesalahan yang layak. Itu hanya mengatakan
error C2118: negative subscript
. Bitfield dengan lebar nol bekerja lebih baik dalam hal ini. Karena ini melibatkan pengetikan sebuah struct, kita benar-benar perlu menggunakan nama tipe yang unik.__LINE__
tidak memotong mustard - dimungkinkan untuk memilikiCOMPILE_TIME_ASSERT()
baris yang sama di header dan file sumber, dan kompilasi Anda akan rusak.__COUNTER__
datang untuk menyelamatkan (dan sudah di gcc sejak 4.3).#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__)
Sekarang
STATIC_ASSERT(sizeof(long)==7, use_another_compiler_luke)
di bawah
cl
memberi:Gcc juga memberikan pesan yang jelas:
sumber
Dari Wikipedia :
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;} COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
sumber
Saya TIDAK akan merekomendasikan menggunakan solusi menggunakan
typedef
:#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]
Deklarasi array dengan
typedef
kata kunci TIDAK dijamin akan dievaluasi pada waktu kompilasi. Misalnya, kode berikut dalam cakupan blok akan dikompilasi:int invalid_value = 0; STATIC_ASSERT(invalid_value, this_should_fail_at_compile_time_but_will_not);
Saya akan merekomendasikan ini sebagai gantinya (pada C99):
#define STATIC_ASSERT(COND,MSG) static int static_assertion_##MSG[(COND)?1:-1]
Karena
static
kata kunci, array akan ditentukan pada waktu kompilasi. Perhatikan bahwa pernyataan ini hanya akan berfungsiCOND
yang dievaluasi pada waktu kompilasi. Ini tidak akan bekerja dengan (yaitu kompilasi akan gagal) dengan kondisi yang didasarkan pada nilai dalam memori, seperti nilai yang diberikan ke variabel.sumber
Jika menggunakan makro STATIC_ASSERT () dengan
__LINE__
, dimungkinkan untuk menghindari pertentangan nomor baris antara entri dalam file .c dan entri berbeda dalam file header dengan menyertakan__INCLUDE_LEVEL__
.Sebagai contoh :
/* 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]
sumber
Cara klasik menggunakan array:
char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1];
Ini berfungsi karena jika pernyataannya benar, array memiliki ukuran 1 dan itu valid, tetapi jika salah ukuran -1 memberikan kesalahan kompilasi.
Kebanyakan kompiler akan menampilkan nama variabel dan menunjuk ke bagian kanan kode di mana Anda bisa meninggalkan komentar tentang pernyataan tersebut.
sumber
#define STATIC_ASSERT()
makro tipe generik dan memberikan contoh yang lebih umum dan keluaran kompiler sampel dari contoh umum Anda menggunakanSTATIC_ASSERT()
akan memberi Anda lebih banyak suara positif dan membuat teknik ini lebih masuk akal menurut saya.Dari Perl, khususnya
perl.h
baris 3455 (<assert.h>
sudah termasuk sebelumnya):/* 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
Jika
static_assert
tersedia (dari<assert.h>
), itu digunakan. Sebaliknya, jika kondisinya salah, bit-field dengan ukuran negatif dideklarasikan, yang menyebabkan kompilasi gagal.STMT_START
/STMT_END
adalah makro yang diperluas kedo
/while (0)
, masing-masing.sumber
Karena:
_Static_assert()
sekarang didefinisikan dalam gcc untuk semua versi C, danstatic_assert()
didefinisikan dalam C ++ 11 dan yang lebih baruMakro sederhana berikut untuk
STATIC_ASSERT()
bekerja di:g++ -std=c++11
) atau yang lebih barugcc -std=c90
gcc -std=c99
gcc -std=c11
gcc
(tidak ada standar yang ditentukan)Definisikan
STATIC_ASSERT
sebagai berikut:/* 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")
Sekarang gunakan:
STATIC_ASSERT(1 > 2); // Output will look like: error: static assertion failed: "(1 > 2) failed"
Contoh:
Diuji di Ubuntu menggunakan gcc 4.8.4:
Contoh 1:
gcc
keluaran yang baik (yaitu:STATIC_ASSERT()
kode berfungsi, tetapi kondisinya salah, menyebabkan pernyataan waktu kompilasi):Contoh 2:
g++ -std=c++11
keluaran yang baik (yaitu:STATIC_ASSERT()
kode berfungsi, tetapi kondisinya salah, menyebabkan pernyataan waktu kompilasi):Contoh 3: keluaran C ++ gagal (yaitu: kode pernyataan tidak berfungsi sama sekali, karena ini menggunakan versi C ++ sebelum C ++ 11):
Hasil tes lengkap di sini:
/* 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; }
Terkait:
sumber
static_assert
makroassert.h
?static_assert()
tidak tersedia sama sekali di C. Lihat juga di sini: en.cppreference.com/w/cpp/language/static_assert - itu menunjukkanstatic_assert
ada "(sejak C ++ 11)". Keunggulan dari jawaban saya adalah bahwa ini berfungsi di gcc's C90 dan yang lebih baru, serta C ++ 11 dan yang lebih baru, bukan hanya di C ++ 11 dan yang lebih baru, sepertistatic_assert()
. Juga, apa rumitnya jawaban saya? Ini hanya beberapa#define
s.static_assert
didefinisikan dalam C sejak C11. Ini adalah makro yang diperluas ke_Static_assert
. en.cppreference.com/w/c/error/static_assert . Selain itu dan kontras dengan jawaban Anda_Static_assert
tidak tersedia di c99 dan c90 di gcc (hanya di gnu99 dan gnu90). Ini sesuai dengan standar. Pada dasarnya Anda melakukan banyak pekerjaan tambahan, yang hanya akan membawa keuntungan jika dikompilasi dengan gnu90 dan gnu99 dan yang membuat usecase sebenarnya tidak terlalu kecil.Bagi Anda yang menginginkan sesuatu yang sangat mendasar dan portabel tetapi tidak memiliki akses ke fitur C ++ 11, saya telah menulisnya.
Gunakan secara
STATIC_ASSERT
normal (Anda dapat menulisnya dua kali dalam fungsi yang sama jika Anda mau) dan gunakan diGLOBAL_STATIC_ASSERT
luar fungsi dengan frase unik sebagai parameter pertama.#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; }
Penjelasan:
Pertama, ia memeriksa apakah Anda memiliki assert yang sebenarnya, yang pasti ingin Anda gunakan jika tersedia.
Jika Anda tidak melakukannya dengan tegas dengan mendapatkan
pred
icate Anda , dan membaginya dengan sendirinya. Ini melakukan dua hal.Jika nol, id est, pernyataan gagal, itu akan menyebabkan kesalahan bagi dengan nol (aritmatika dipaksa karena mencoba mendeklarasikan sebuah array).
Jika bukan nol, itu menormalkan ukuran array menjadi
1
. Jadi jika assertion berhasil, Anda tidak ingin itu gagal karena predikat Anda dievaluasi menjadi-1
(tidak valid), atau menjadi232442
(pemborosan ruang yang sangat besar, IDK jika itu akan dioptimalkan).Sebab , artinya Anda bisa menulisnya berkali-kali. Itu juga melemparkannya ke
STATIC_ASSERT
itu dibungkus dengan tanda kurung, ini membuatnya menjadi blok, yang mencakup variabelassert
void
, yang merupakan cara yang dikenal untuk menghilangkanunused variable
peringatan.Karena
GLOBAL_STATIC_ASSERT
, alih-alih berada dalam blok kode, ia menghasilkan namespace. Namespaces diperbolehkan di luar fungsi. Sebuahunique
identifier diperlukan untuk menghentikan definisi bertentangan jika Anda menggunakan satu ini lebih dari sekali.Bekerja untuk saya di GCC dan VS'12 C ++
sumber
Ini berfungsi, dengan set opsi "hapus yang tidak digunakan". Saya dapat menggunakan satu fungsi global untuk memeriksa parameter global.
// #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 //
sumber
Ini berhasil untuk beberapa gcc lama. Maaf saya lupa versi apa itu:
#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
sumber