Pertanyaan Apa itu ": - !!" dalam kode C?


Saya menabrak kode makro aneh ini di /usr/include/linux/kernel.h:

/* Force a compilation error if condition is true, but also produce a
   result (of value 0 and type size_t), so the expression can be used
   e.g. in a structure initializer (or where-ever else comma expressions
   aren't permitted). */
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))

Apa yang terjadi :-!! melakukan?


1476
2018-02-10 14:50


asal


Jawaban:


Ini, pada dasarnya, cara untuk memeriksa apakah ekspresi e dapat dievaluasi menjadi 0, dan jika tidak, gagal membangunnya.

Makro ini agak salah diartikan; seharusnya sesuatu yang lebih seperti itu BUILD_BUG_OR_ZERO, daripada ...ON_ZERO. (Telah ada diskusi sesekali tentang apakah ini adalah nama yang membingungkan.)

Anda harus membaca ekspresi seperti ini:

sizeof(struct { int: -!!(e); }))
  1. (e): Hitung ekspresi e.

  2. !!(e): Menolak secara logis dua kali: 0 jika e == 0; jika tidak 1.

  3. -!!(e): Menyesatkan ekspresi secara numerik dari langkah 2: 0 Andai saja 0; jika tidak -1.

  4. struct{int: -!!(0);} --> struct{int: 0;}: Jika nol, maka kita mendeklarasikan struct dengan bitfield integer anonim yang memiliki lebar nol. Semuanya baik-baik saja dan kita lanjutkan seperti biasa.

  5. struct{int: -!!(1);} --> struct{int: -1;}: Di sisi lain, jika itu tidak nol, maka itu akan menjadi beberapa angka negatif. Mendeklarasikan bitfield dengan negatif lebar adalah kesalahan kompilasi.

Jadi kita akan berakhir dengan bitfield yang memiliki lebar 0 dalam sebuah struct, yang bagus, atau bitfield dengan lebar negatif, yang merupakan kesalahan kompilasi. Lalu kita ambil sizeof bidang itu, jadi kami mendapatkan size_t dengan lebar yang sesuai (yang akan menjadi nol dalam kasus di mana e adalah nol).


Beberapa orang bertanya: Kenapa tidak pakai saja assert?

jawaban keithmo di sini ada respons yang bagus:

Makro ini menerapkan uji waktu kompilasi, sementara assert () adalah uji run-time.

Tepat sekali. Anda tidak ingin mendeteksi masalah pada Anda inti pada saat runtime yang bisa ditangkap sebelumnya! Ini adalah bagian penting dari sistem operasi. Untuk masalah sejauh mana dapat dideteksi pada waktu kompilasi, jauh lebih baik.


1530
2018-02-10 15:04



Itu : adalah sebuah bitfield. Seperti untuk !!, itu adalah negasi ganda logis dan kembali begitu 0 untuk false atau 1 untuk benar. Dan itu - adalah tanda minus, yaitu negasi aritmatika.

Itu semua hanya trik untuk mendapatkan kompilator untuk muntah pada input yang tidak valid.

Mempertimbangkan BUILD_BUG_ON_ZERO. Kapan -!!(e) mengevaluasi ke nilai negatif, yang menghasilkan kesalahan kompilasi. Jika tidak -!!(e) mengevaluasi ke 0, dan bitfield lebar 0 memiliki ukuran 0. Dan karenanya makro mengevaluasi ke a size_t dengan nilai 0.

Namanya lemah dalam pandangan saya karena build pada kenyataannya gagal ketika input tersebut tidak nol.

BUILD_BUG_ON_NULL sangat mirip, tetapi menghasilkan pointer daripada int.


235
2018-02-10 14:54



Beberapa orang tampaknya membingungkan makro ini assert().

Makro ini menerapkan uji waktu kompilasi, sementara assert() adalah uji waktu proses.


149
2018-02-10 15:37



Yah, saya cukup terkejut bahwa alternatif untuk sintaks ini belum disebutkan. Mekanisme lain yang umum (tetapi lebih tua) adalah memanggil fungsi yang tidak didefinisikan dan bergantung pada pengoptimal untuk mengkompilasi fungsi panggilan jika pernyataan Anda benar.

#define MY_COMPILETIME_ASSERT(test)              \
    do {                                         \
        extern void you_did_something_bad(void); \
        if (!(test))                             \
            you_did_something_bad(void);         \
    } while (0)

Sementara mekanisme ini berfungsi (selama optimisasi diaktifkan), ia memiliki sisi negatif karena tidak melaporkan kesalahan sampai Anda menautkan, pada saat itu gagal menemukan definisi untuk fungsi Anda_did_something_bad (). Itu sebabnya pengembang kernel mulai menggunakan trik seperti lebar bidang bit ukuran negatif dan larik berukuran negatif (yang kemudian berhenti membangun di GCC 4.4).

Bersimpati pada kebutuhan untuk pernyataan waktu kompilasi, GCC 4.3 memperkenalkan error atribut fungsi yang memungkinkan Anda untuk memperluas konsep yang lebih tua ini, tetapi menghasilkan kesalahan waktu kompilasi dengan pesan yang Anda pilih - tidak ada lagi pesan kesalahan "ukuran negatif" yang samar-samar!

#define MAKE_SURE_THIS_IS_FIVE(number)                          \
    do {                                                        \
        extern void this_isnt_five(void) __attribute__((error(  \
                "I asked for five and you gave me " #number))); \
        if ((number) != 5)                                      \
            this_isnt_five();                                   \
    } while (0)

Bahkan, pada Linux 3.9, kami sekarang memiliki makro yang disebut compiletime_assert yang menggunakan fitur ini dan sebagian besar makro bug.h telah diperbarui sesuai. Namun, makro ini tidak dapat digunakan sebagai penginisialisasi. Namun, digunakan oleh pernyataan ekspresi (GCC C-ekstensi lain), Anda bisa!

#define ANY_NUMBER_BUT_FIVE(number)                           \
    ({                                                        \
        typeof(number) n = (number);                          \
        extern void this_number_is_five(void) __attribute__(( \
                error("I told you not to give me a five!"))); \
        if (n == 5)                                           \
            this_number_is_five();                            \
        n;                                                    \
    })

Makro ini akan mengevaluasi parameternya tepat satu kali (jika ada efek samping) dan membuat kesalahan waktu kompilasi yang mengatakan, "Saya bilang jangan memberi saya lima!" jika ekspresi mengevaluasi hingga lima atau bukan konstanta waktu kompilasi.

Jadi mengapa kita tidak menggunakan ini daripada bit-field yang berukuran negatif? Sayangnya, saat ini ada banyak pembatasan penggunaan pernyataan ekspresi, termasuk penggunaannya sebagai initializers konstan (untuk konstanta enum, lebar bidang-bit, dll) bahkan jika ekspresi pernyataan benar-benar konstan dirinya (yaitu, dapat sepenuhnya dievaluasi pada waktu kompilasi dan sebaliknya melewati __builtin_constant_p() uji). Lebih lanjut, mereka tidak dapat digunakan di luar badan fungsi.

Semoga, GCC akan memperbaiki kekurangan ini segera dan memungkinkan ekspresi pernyataan konstan untuk digunakan sebagai initializers konstan. Tantangannya di sini adalah spesifikasi bahasa yang mendefinisikan apa yang merupakan ekspresi konstan hukum. C ++ 11 menambahkan kata kunci constexpr hanya untuk jenis atau hal ini, tetapi tidak ada mitra yang ada di C11. Sementara C11 tidak mendapatkan pernyataan statis, yang akan menyelesaikan sebagian dari masalah ini, itu tidak akan menyelesaikan semua kekurangan ini. Jadi saya berharap bahwa gcc dapat membuat fungsi constexpr tersedia sebagai ekstensi melalui -std = gnuc99 & -std = gnuc11 atau semacamnya dan memungkinkan penggunaannya pada pernyataan ekspresi et. Al.


42
2018-06-27 08:21



Ini menciptakan ukuran 0 bitfield jika kondisinya salah, tetapi ukuran -1 (-!!1) bitfield jika kondisinya benar / tidak nol. Dalam kasus sebelumnya, tidak ada kesalahan dan struct diinisialisasi dengan anggota int. Dalam kasus terakhir, ada kesalahan kompilasi (dan tidak ada yang namanya ukuran -1 bitfield dibuat, tentu saja).


31
2018-02-10 14:54



 Linux Kernel :   

/* Force a compilation error if condition is true, but also produce a
   result (of value 0 and type size_t), so the expression can be used
   e.g. in a structure initializer (or where-ever else comma expressions
   aren't permitted). */

#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))

-1
2018-06-21 07:18