Nedir ":-!!" C kodunda?


1665

Ben de bu garip makro kod çarptı /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); }))

Ne yapar :-!!?


2
-Tek tek eksi <br />! Mantıksal DEĞİL <br /> verilen
Tamsayı'nın

69
git blame bize bu özel statik iddianın Jan Beulich tarafından 8c87df4'te tanıtıldığını söylüyor . Açıkçası bunu yapmak için iyi nedenleri vardı (taahhüt mesajına bakınız).
Niklas B.10

55
@Lundin: assert (), derleme zamanı hatasına neden OLMAZ. Yukarıdaki yapının bütün mesele bu.
Chris Pacejo

4
@GreweKokkor Saf olma, Linux bir kişinin hepsini halletmesi için çok büyük. Linus'un yardımcıları var ve aşağıdan yukarıya doğru değişiklikleri ve iyileştirmeleri itenlere sahipler. Linus sadece özellik isteyip istemediğine karar verir, ancak iş arkadaşlarına bir dereceye kadar güvenir. Dağıtılmış sistemin açık kaynak ortamında nasıl çalıştığı hakkında daha fazla bilgi edinmek istiyorsanız youtube videosunu kontrol edin: youtube.com/watch?v=4XpnKHJAok8 (Bu çok ilginç bir konuşmadır).
Tomas Pruzina

3
@cpcloud, sizeofdeğeri değil, türü "değerlendirir". Bu tip bu durumda geçersiz.
Winston Ewert

Yanıtlar:


1692

Bu aslında, e ifadesinin 0 olarak değerlendirilip değerlendirilemeyeceğini kontrol etmenin bir yoludur ve eğer yapılamıyorsa başarısız olur .

Makro biraz yanlış adlandırılmış; daha BUILD_BUG_OR_ZEROziyade benzer bir şey olmalı ...ON_ZERO. ( Bunun kafa karıştırıcı bir isim olup olmadığı konusunda zaman zaman tartışmalar olmuştur .)

İfadeyi şu şekilde okumalısınız:

sizeof(struct { int: -!!(e); }))
  1. (e): Hesaplama ifadesi e.

  2. !!(e): Mantıksal olarak iki kez olumsuzlayın: 0if e == 0; aksi takdirde 1.

  3. -!!(e): Adım 2'deki ifadeyi sayısal olarak olumsuzlayın: 0öyleyse 0; aksi takdirde -1.

  4. struct{int: -!!(0);} --> struct{int: 0;}: Sıfırsa, genişliği sıfır olan anonim tamsayı bit alanına sahip bir yapı bildiririz. Her şey yolunda ve normal şekilde ilerliyoruz.

  5. struct{int: -!!(1);} --> struct{int: -1;}: Öte yandan, eğer sıfır değilse , o zaman negatif bir sayı olacaktır. Negatif genişliğe sahip herhangi bir bit alanının bildirilmesi bir derleme hatasıdır.

Bu yüzden ya bir yapıda genişliği 0 olan bir bit alanı ile, ya da bir derleme hatası olan negatif genişliği olan bir bit alanı ile sararız. Sonra sizeofbu alanı alırız, bu yüzden size_tuygun genişliğe sahip bir a alırız (sıfır olduğu edurumda sıfır olacaktır).


Bazı insanlar sordu: Neden sadece bir assert?

keithmo'nun cevabının iyi bir yanıtı var:

Bu makrolar bir derleme zamanı testi uygularken assert () bir çalışma zamanı testidir.

Kesinlikle doğru. Çalışma zamanında çekirdeğinizde daha önce yakalanmış olabilecek sorunları tespit etmek istemezsiniz ! İşletim sisteminin kritik bir parçası. Derleme zamanında sorunlar ne ölçüde tespit edilebilirse o kadar iyidir.


5
@weston Birçok farklı yer. Kendin için gör!
John Feminella

166
C ++ veya C standartlarının son varyantları static_assertilgili amaçlar için benzer bir şeye sahiptir .
Basile Starynkevitch

54
@Lundin - #error, / # error / # endif ise 3 satırlık # kod satırı gerektirir ve yalnızca ön işlemcinin erişebileceği değerlendirmeler için çalışır. Bu kesmek derleyici tarafından erişilebilir herhangi bir değerlendirme için çalışır.
Ed Staub

236
Linux çekirdeği en azından Linus hala hayatta iken C ++ kullanmaz.
Mark Ransom

6
@ Dolda2000: "C'deki Boole ifadeleri her zaman sıfır veya bir olarak değerlendirilecek şekilde tanımlanır " - Tam olarak değil. Operatörler verim "mantıksal boolean" sonuçları (yani !, <, >, <=, >=, ==, !=, &&, ||) her zaman bir koşulları olarak kullanılabilir sonuçlar doğurabilir 0 veya 1 diğer ifadeler, verim ancak sadece sıfır ya da sıfırdan farklı olduğu; Örneğin isdigit(c), burada cbir rakam olduğu, verebilmesidir bir (daha sonra bir durumda doğru olarak kabul edilir), sıfır olmayan bir değer.
Keith Thompson

256

:Bir saklayıcısında olduğunu. Gelince !!, bu mantıksal çift olumsuzlamadır ve bu yüzden 0yanlış veya 1doğru için geri döner . Ve -bir eksi işaretidir, yani aritmetik olumsuzlama.

Derleyiciyi geçersiz girdilere engellemenin tek yolu bir numara.

Düşünün BUILD_BUG_ON_ZERO. Ne zaman -!!(e)bir derleme hatası üretir negatif bir değer, olarak değerlendirilir. Aksi takdirde -!!(e)0 olarak değerlendirilir ve 0 genişlikli bir bit alanı 0 büyüklüğüne sahiptir ve bu nedenle makro size_t, 0 değerine sahip bir a olarak değerlendirir .

Benim görüşüme göre isim zayıf çünkü giriş sıfır olmadığında aslında yapı başarısız olur .

BUILD_BUG_ON_NULLçok benzerdir, ancak bir yerine bir işaretçi verir int.


14
olduğunu sizeof(struct { int:0; })kesinlikle uygun?
ouah

7
Sonuç neden genel olarak 0? A structsadece boş bir bit alanı ile, doğru, ama ben 0 boyutu ile yapı izin olduğunu sanmıyorum. Örneğin, bu türde bir dizi oluşturursanız, tek tek dizi öğelerinin hala farklı adresleri olmalıdır, değil mi?
Jens Gustedt

2
aslında GNU uzantılarını kullandıkları için umursamazlar, katı takma kuralını devre dışı bırakırlar ve tamsayı taşmalarını UB olarak kabul etmezler. Ama bunun kesinlikle C ile uyumlu olup olmadığını merak ediyordum
ouah

3
İsimsiz sıfır uzunluktaki bit alanlarıyla ilgili @ouah, buraya bakın: stackoverflow.com/questions/4297095/…
David Heffernan

9
@DavidHeffernan aslında C, adlandırılmamış 0genişlik bit alanına izin verir , ancak yapıda başka bir adlandırılmış üye yoksa izin vermez . (C99, 6.7.2.1p2) "If the struct-declaration-list contains no named members, the behavior is undefined."Yani, örneğin sizeof (struct {int a:1; int:0;}), kesinlikle uyumludur, ancak sizeof(struct { int:0; })değildir (tanımlanmamış davranış).
ouah

168

Bazı insanlar bu makroları karıştırıyor gibi görünüyor assert().

Bu makrolar assert()bir çalışma zamanı testi yaparken derleme zamanı testi uygular .


52

Bu sözdizimine alternatiflerden bahsetmediğim için oldukça şaşırdım. Başka bir yaygın (ancak daha eski) mekanizma, iddianız doğruysa işlev çağrısını derlemek için tanımlanmamış bir işlevi çağırmak ve optimize ediciye güvenmektir.

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

Bu mekanizma çalışırken (optimizasyonlar etkin olduğu sürece) bağlantı yapana kadar bir hata raporlamamanın dezavantajı vardır, bu sırada you_did_something_bad () işlevinin tanımını bulamaz. Bu nedenle çekirdek geliştiricileri, negatif boyutlu bit alanı genişlikleri ve negatif boyutlu diziler (daha sonra GCC 4.4'te yapıları kırmayı durduran) gibi hileler kullanmaya başlayanlar.

Derleme zamanı iddialarına duyulan sempati duyulduğunda, GCC 4.3 , bu eski konsepte uzanmanıza izin veren errorfonksiyon özelliğini tanıttı , ancak seçtiğiniz bir mesajla derleme zamanı hatası oluşturdu - artık şifreli olmayan "negatif boyutlu dizi " hata mesajları!

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

Aslında, Linux 3.9'dan itibaren, şimdi compiletime_assertbu özelliği kullanan bir makro var ve makroların çoğu bug.hbuna göre güncellendi. Yine de, bu makro bir başlatıcı olarak kullanılamaz. Ancak, deyim ifadeleriyle (başka bir GCC C uzantısı) kullanarak yapabilirsiniz!

#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;                                                    \
    })

Bu makro parametresini tam olarak bir kez değerlendirir (yan etkileri olması durumunda) ve "Bana beş tane vermemeni söylemiştim!" ifade beş olarak değerlendirilirse veya derleme zamanı sabiti değilse.

Peki bunu neden negatif boyutlu bit alanları yerine kullanmıyoruz? Ne yazık ki, ifade ifadesi tamamen kendiliğinden sabit olsa bile (yani, tam olarak değerlendirilebilir), sabit başlatıcılar (numara sabitleri, bit-alan genişliği, vb. İçin) olarak kullanımları da dahil olmak üzere, ifade ifadelerinin kullanımında birçok kısıtlama vardır. derleme zamanında ve aksi takdirde __builtin_constant_p()testi geçer ). Ayrıca, bir işlev gövdesi dışında kullanılamazlar.

Umarım, GCC bu eksiklikleri yakında değiştirecek ve sabit deyim ifadelerinin sabit başlatıcılar olarak kullanılmasına izin verecektir. Buradaki zorluk, yasal sabit bir ifadenin ne olduğunu tanımlayan dil belirtimidir. C ++ 11, yalnızca bu tür veya şey için constexpr anahtar sözcüğünü ekledi, ancak C11'de herhangi bir karşılık bulunmuyor. C11, bu sorunun bir kısmını çözecek statik iddialar alırken, tüm bu eksiklikleri çözmeyecektir. Bu yüzden umuyorum ki gcc bir constexpr işlevselliğini -std = gnuc99 & -std = gnuc11 ya da bu gibi bir uzantı olarak kullanılabilir hale getirebilir ve deyim ifadeleri et. ark.


6
Tüm çözümleriniz alternatif DEĞİLDİR. Makronun üstündeki yorum oldukça açık " so the expression can be used e.g. in a structure initializer (or where-ever else comma expressions aren't permitted)." Makro, bir tür ifade döndürüyorsize_t
Wiz

3
@Wiz Evet, bunun farkındayım. Belki de bu biraz ayrıntılıydı ve belki de ifademi tekrar ziyaret etmem gerekiyor, ama benim amacım statik iddialar için çeşitli mekanizmaları keşfetmek ve neden hala negatif boyutlu bitfield kullandığımızı göstermekti. Kısacası, sürekli ifade ifadesi için bir mekanizma alırsak, başka seçenekler de açık olacaktır.
Daniel Santos

Her neyse, bu makroyu bir değişken için kullanamayız. sağ? error: bit-field ‘<anonymous>’ width not an integer constantSadece sabitlere izin veriyor. Peki, ne işe yarar?
Karthik Raj Palanichamy

1
@Karthik Neden kullanıldığını görmek için Linux çekirdeğinin kaynaklarını arayın.
Daniel Santos

@supercat Yorumunuzun nasıl alakalı olduğunu anlamıyorum. Lütfen gözden geçirebilir, ne demek istediğinizi daha iyi açıklayabilir veya kaldırabilir misiniz?
Daniel Santos

36

0Koşul yanlışsa bir boyut bit alanı oluşturur , ancak koşul doğru / sıfır değilse bir boyut -1( -!!1) bit alanı oluşturur. Önceki durumda hata yoktur ve yapı int üyesiyle başlatılır. İkinci durumda, bir derleme hatası vardır (ve -1elbette bir boyut bit alanı oluşturulmaz).


3
Durumun doğru size_tolması durumunda aslında 0 değeri olan bir döndürüyor .
David Heffernan
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.