Apa cara terbaik untuk mencapai pernyataan statis waktu kompilasi di C (bukan C ++), dengan penekanan khusus pada GCC?
Apa cara terbaik untuk mencapai pernyataan statis waktu kompilasi di C (bukan C ++), dengan penekanan khusus pada GCC?
Jawaban:
Standar C11 menambahkan _Static_assert
kata kunci.
Ini diterapkan sejak gcc-4.6 :
_Static_assert (0, "assert1"); /* { dg-error "static assertion failed: \"assert1\"" } */
Slot pertama harus berupa ekspresi konstanta integral. Slot kedua adalah literal string konstan yang bisa panjang ( _Static_assert(0, L"assertion of doom!")
).
Saya harus mencatat bahwa ini juga diimplementasikan dalam versi terbaru dari clang.
error: expected declaration specifiers or '...' before 'sizeof'
untuk jalur static_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'
AND error: 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 ++ ish static_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 akhir static_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
-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!
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 memiliki COMPILE_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:
kesalahan C2149: 'static_assertion_failed_use_another_compiler_luke': bidang bit bernama tidak boleh memiliki lebar nol
Gcc juga memberikan pesan yang jelas:
kesalahan: lebar nol untuk bit-field 'static_assertion_failed_use_another_compiler_luke'
Dari Wikipedia :
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}
COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
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 berfungsi COND
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.
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]
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.
#define STATIC_ASSERT()
makro tipe generik dan memberikan contoh yang lebih umum dan keluaran kompiler sampel dari contoh umum Anda menggunakan STATIC_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 ke do
/ while (0)
, masing-masing.
_Static_assert()
sekarang didefinisikan dalam gcc untuk semua versi C, dan static_assert()
didefinisikan dalam C ++ 11 dan yang lebih baruSTATIC_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"
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):
$ gcc -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c: Dalam fungsi 'main'
static_assert.c: 78: 38: kesalahan: pernyataan statis gagal: "(1> 2) gagal"
#define STATIC_ASSERT (test_for_true ) _Static_assert ((test_for_true), "(" #test_for_true ") gagal")
^
static_assert.c: 88: 5: catatan: dalam perluasan makro 'STATIC_ASSERT'
STATIC_ASSERT (1> 2);
^
Contoh 2:g++ -std=c++11
keluaran yang baik (yaitu: STATIC_ASSERT()
kode berfungsi, tetapi kondisinya salah, menyebabkan pernyataan waktu kompilasi):
$ g ++ -Wall -std = c ++ 11 -o static_assert static_assert.c && ./static_assert
static_assert.c: Dalam fungsi 'int main ()'
static_assert.c: 74: 32: kesalahan: pernyataan statis gagal: (1> 2) gagal
#define _Static_assert static_assert / *static_assert
adalah bagian dari C ++ 11 atau lebih baru * /
^
static_assert.c: 78: 38: catatan: dalam perluasan makro '_Static_assert'
#define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true ") gagal")
^
static_assert.c: 88: 5: catatan: dalam perluasan makro 'STATIC_ASSERT'
STATIC_ASSERT (1> 2);
^
Contoh 3: keluaran C ++ gagal (yaitu: kode pernyataan tidak berfungsi sama sekali, karena ini menggunakan versi C ++ sebelum C ++ 11):
$ g ++ -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c: 88: 5: peringatan: pengidentifikasi 'static_assert' adalah kata kunci dalam C ++ 11 [-Wc ++ 0x-compat]
STATIC_ASSERT (1> 2 );
^
static_assert.c: Dalam fungsi 'int main ()'
static_assert.c: 78: 99: error: 'static_assert' tidak dideklarasikan dalam lingkup ini
#define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true " ) gagal ")
^
static_assert.c: 88: 5: catatan: dalam perluasan makro '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
makro assert.h
?
static_assert()
tidak tersedia sama sekali di C. Lihat juga di sini: en.cppreference.com/w/cpp/language/static_assert - itu menunjukkan static_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, seperti static_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 di GLOBAL_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 menjadi 232442
(pemborosan ruang yang sangat besar, IDK jika itu akan dioptimalkan).
Sebab , artinya Anda bisa menulisnya berkali-kali.
Itu juga melemparkannya keSTATIC_ASSERT
itu dibungkus dengan tanda kurung, ini membuatnya menjadi blok, yang mencakup variabelassert
void
, yang merupakan cara yang dikenal untuk menghilangkan unused variable
peringatan.
Karena GLOBAL_STATIC_ASSERT
, alih-alih berada dalam blok kode, ia menghasilkan namespace. Namespaces diperbolehkan di luar fungsi. Sebuah unique
identifier diperlukan untuk menghentikan definisi bertentangan jika Anda menggunakan satu ini lebih dari sekali.
Bekerja untuk saya di GCC dan VS'12 C ++
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
//
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
_Static_assert
adalah bagian dari standar C11 dan semua kompilator yang mendukung C11, akan memilikinya.