Penegasan statis di C

87

Apa cara terbaik untuk mencapai pernyataan statis waktu kompilasi di C (bukan C ++), dengan penekanan khusus pada GCC?

Matt Joiner
sumber

Jawaban:

91

Standar C11 menambahkan _Static_assertkata kunci.

Ini diterapkan sejak gcc-4.6 :

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.

emsr
sumber
4
[... tampaknya diimplementasikan oleh gcc, dengan clang ...] Anda dapat lebih tegas bahwa ;-) _Static_assertadalah bagian dari standar C11 dan semua kompilator yang mendukung C11, akan memilikinya.
PP
1
Bisakah ini digunakan pada ruang lingkup file (di luar fungsi apa pun)? Karena saya mendapatkan error: expected declaration specifiers or '...' before 'sizeof'untuk jalur static_assert( sizeof(int) == sizeof(long int), "Error!); (omong-omong saya menggunakan C bukan C ++)
user10607
@ user10607 Saya terkejut ini tidak berhasil .. Tunggu, Anda kehilangan kutipan di akhir string kesalahan Anda. Masukkan itu dan dapatkan kembali. Ini berfungsi untuk saya di gcc-4.9: _Static_assert( sizeof(int) == sizeof(long int), "Error!");Di macine saya, saya mendapatkan kesalahan.
emsr
Saya memiliki gcc 4.8.2 di Ubuntu. Kutipan yang hilang adalah kesalahan ketik komentar (saya memilikinya dalam kode). Ini adalah baris pertama dalam file setelah beberapa header disertakan. Kompiler memberi saya dua kesalahan yang sama persis: 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))
user10607
2
@ user10607 Saya juga harus menentukan -std = gnu11 pada baris perintah. Saya sangat terkejut akan ada perbedaan antara 4.8 dan 4.8. Saya memiliki sumber hanya dengan satu baris. Saya juga menggunakan standar C _Static_assertbukan C ++ ish static_assert. Anda perlu `#include <assert.h> untuk mendapatkan makro static_assert.
emsr
93

Ini berfungsi dalam lingkup fungsi dan non-fungsi (tetapi tidak di dalam struct, unions).

  1. 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

  2. Makro bisa atau harus diubah untuk menghasilkan nama unik untuk typedef (yaitu gabungan __LINE__di akhir static_assert_...nama)

  3. 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__

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:

Dan seperti inilah tampilannya:

Mainframe Nordik
sumber
1
Dalam Visual Studio hanya mengatakan "Subskrip negatif", tidak menyebutkan nama variabel ...
szx
Nordic Mainframe - opsi 3 dalam jawaban Anda tidak berfungsi pada dentang.
Elazar
1
Mengenai solusi terakhir (khusus GCC 4.3 +): Ini sangat kuat, karena dapat memeriksa apa saja yang dapat diketahui oleh pengoptimal, tetapi gagal jika pengoptimalan tidak diaktifkan. Tingkat pengoptimalan minimal ( -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.
Søren Løvborg
Dalam cuplikan Kode dengan versi LINE (UPDATE: Demi kelengkapan, inilah versi dengan `LINE), saat kompilasi, ada kesalahan pada baris (STATIC_ASSERT (X, static_assertion_at_line _ ## L)), yang dapat diperbaiki dengan menambahkan satu lagi tingkat seperti di bawah ini: #define COMPILE_TIME_ASSERT4 (X, L) static_assert (X, # L); #define COMPILE_TIME_ASSERT3 (X, L) COMPILE_TIME_ASSERT3 (X, "" Assertion at: ## L "");
Minggu
Saya menggunakan sesuatu yang mirip dengan __LINE__versi di gcc 4.1.1 ... dengan gangguan sesekali ketika dua header berbeda kebetulan memiliki satu di baris nomor yang sama!
MM
10

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 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).

Sekarang

di bawah clmemberi:

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'

bobbogo.dll
sumber
4

Dari Wikipedia :

Tyler
sumber
15
Akan lebih baik jika Anda menautkan ke sumber yang sebenarnya: jaggersoft.com/pubs/CVu11_3.html
Matt Joiner
Ini tidak bekerja di gcc 4.6 - dikatakan "label kasus tidak berkurang menjadi konstanta integer". Itu ada benarnya.
Liosan
Anda berdua mungkin sudah pindah sekarang, tapi akhirnya saya menulis sendiri (lihat jawaban saya ). Saya menggunakan tautan Anda @MattJoiner untuk membantu saya
Hashbrown
Dan jika Anda bisa terganggu, beri tahu saya jika itu berhasil untuk Anda, @Liosan. Saya baru saja mulai mempelajari C ++ jadi saya datang terlambat ke pesta
Hashbrown
Adapun Visual C ++, ini memiliki static_assert built-in sejak versi 2010, dan bekerja di mode c ++ dan c. Namun, ini tidak memiliki c99 _Static_assert bawaan.
ddbug
3

Saya TIDAK akan merekomendasikan menggunakan solusi menggunakan typedef:

Deklarasi array dengan typedefkata kunci TIDAK dijamin akan dievaluasi pada waktu kompilasi. Misalnya, kode berikut dalam cakupan blok akan dikompilasi:

Saya akan merekomendasikan ini sebagai gantinya (pada C99):

Karena statickata kunci, array akan ditentukan pada waktu kompilasi. Perhatikan bahwa pernyataan ini hanya akan berfungsi CONDyang 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.

FredFredFredFred
sumber
4
Meskipun ini akan berhasil, itu juga akan meningkatkan kebutuhan memori Anda.
sherrellbc
1
kesalahan: 'static_assertion_INVALID_CHAR_SIZE' ditentukan tetapi tidak digunakan [-Werror = unused-variable]
Alex
2

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 :

BrentNZ
sumber
1

Cara klasik menggunakan array:

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.

Paolo.Bolzoni
sumber
Membungkus ini menjadi #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.
Gabriel Staples
Saya tidak setuju. Kompilator melihat makro pemikiran dan memberikan pesan yang lebih membingungkan.
Paolo. Bolzoni
1

Dari Perl, khususnya perl.hbaris 3455 ( <assert.h>sudah termasuk sebelumnya):

Jika static_asserttersedia (dari <assert.h>), itu digunakan. Sebaliknya, jika kondisinya salah, bit-field dengan ukuran negatif dideklarasikan, yang menyebabkan kompilasi gagal.

STMT_START/ STMT_ENDadalah makro yang diperluas ke do/ while (0), masing-masing.

melpomene
sumber
1

Karena:

  1. _Static_assert() sekarang didefinisikan dalam gcc untuk semua versi C, dan
  2. static_assert() didefinisikan dalam C ++ 11 dan yang lebih baru

Makro sederhana berikut untuk STATIC_ASSERT()bekerja di:

  1. C ++:
    1. C ++ 11 ( g++ -std=c++11) atau yang lebih baru
  2. C:
    1. gcc -std=c90
    2. gcc -std=c99
    3. gcc -std=c11
    4. gcc (tidak ada standar yang ditentukan)

Definisikan STATIC_ASSERTsebagai berikut:

Sekarang gunakan:

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):

$ 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_assertadalah 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);
^

Hasil tes lengkap di sini:

Terkait:

  1. Gunakan static_assert untuk memeriksa jenis yang diteruskan ke makro [jawaban saya sendiri]
    1. https://en.cppreference.com/w/cpp/types/is_same
    2. https://en.cppreference.com/w/cpp/language/decltype
  2. Gunakan static_assert untuk memeriksa jenis yang diteruskan ke makro
  3. Cara menggunakan pernyataan statis di C untuk memeriksa jenis parameter yang diteruskan ke makro
Gabriel Staples
sumber
1
Mengapa begitu rumit, ketika ada static_assertmakro assert.h?
Selamat tinggal SE
@KamiKaze, saya terkejut dengan pertanyaan Anda, karena sepertinya Anda belum benar-benar membaca jawaban saya? Baris ke-2 dari jawaban saya mengatakan itu semua: "static_assert () didefinisikan dalam C ++ 11 dan yang lebih baru". Oleh karena itu, static_assert()tidak tersedia sama sekali di C. Lihat juga di sini: en.cppreference.com/w/cpp/language/static_assert - itu menunjukkan static_assertada "(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 #defines.
Gabriel Staples
static_assertdidefinisikan 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_asserttidak 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.
Selamat tinggal SE
> "_Static_assert tidak tersedia di c99 dan c90 di gcc (hanya di gnu99 dan gnu90)". Saya mengerti apa yang kamu maksud. Ini adalah ekstensi gcc jadi Anda benar. > "Pada dasarnya Anda melakukan banyak pekerjaan ekstra". Saya tidak setuju; 2 definisi yang sangat sederhana sama sekali bukan "banyak" pekerjaan ekstra. Itu dikatakan, saya mengerti apa yang Anda maksud sekarang. Saya masih berpikir apa yang telah saya lakukan berguna dan menambah nilai pada pengetahuan dan jawaban yang disajikan di sini, jadi menurut saya itu tidak pantas untuk downvote. Juga, kesalahan saya dalam mengatakan "C90 dan yang lebih baru" daripada "gcc C90 dan yang lebih baru", atau "g90 dan yang lebih baru", hanya ada pada komentar saya di atas, bukan pada jawaban saya.
Gabriel Staples
Karena secara faktual salah, suara negatif bisa dibenarkan. Jika Anda akan mengoreksi pernyataan yang salah, saya akan memeriksa jawabannya lagi dan mungkin mencabut suara negatif saya. Tetap menambahkan kode tersebut jika tidak perlu (jadi jika Anda tidak bekerja dengan gnu90 dan gnu99) tidak menguntungkan untuk kejelasan dan menambah lebih banyak kekacauan. Jika Anda memiliki kasus penggunaan itu mungkin sepadan. Tapi saya bertanya-tanya tentang kelangkaan kasus penggunaan di mana kompatibilitas gnu99 / 90 dan c ++ 11 diperlukan.
Selamat tinggal SE
0

Bagi Anda yang menginginkan sesuatu yang sangat mendasar dan portabel tetapi tidak memiliki akses ke fitur C ++ 11, saya telah menulisnya.
Gunakan secara STATIC_ASSERTnormal (Anda dapat menulisnya dua kali dalam fungsi yang sama jika Anda mau) dan gunakan di GLOBAL_STATIC_ASSERTluar fungsi dengan frase unik sebagai parameter pertama.


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 predicate 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 variableperingatan.
Karena GLOBAL_STATIC_ASSERT, alih-alih berada dalam blok kode, ia menghasilkan namespace. Namespaces diperbolehkan di luar fungsi. Sebuah uniqueidentifier diperlukan untuk menghentikan definisi bertentangan jika Anda menggunakan satu ini lebih dari sekali.


Bekerja untuk saya di GCC dan VS'12 C ++

Hashbrown
sumber
2
Tidak ada ruang nama di C.
martinkunev
ah, ups, salah baca pertanyaannya. Sepertinya saya datang ke sini mencari jawaban untuk C ++ (melihat baris terakhir jawaban saya), jadi saya akan meninggalkannya di sini kalau-kalau orang lain melakukan hal yang sama
Hashbrown
0

Ini berfungsi, dengan set opsi "hapus yang tidak digunakan". Saya dapat menggunakan satu fungsi global untuk memeriksa parameter global.

pengguna4978854
sumber
1
Jika berhasil, itu hanya akan melakukannya di sumber yang dapat dieksekusi.
Coder
0

Ini berhasil untuk beberapa gcc lama. Maaf saya lupa versi apa itu:

jay
sumber