क्या है ":-!!" सी कोड में?


1664

मैंने इस अजीब मैक्रो कोड को /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); }))

क्या करता :-!!है?


2
- यूनरी माइनस <br />! लॉजिकल
नॉट

69
git blame हमें बताता है कि यह विशेष रूप से स्थैतिक अभिकथन 8c87df4 में Jan Beulich द्वारा पेश किया गया था । जाहिर है कि उसके पास ऐसा करने के अच्छे कारण थे (देखें संदेश)।
निकोलस बी।

55
@ लुंडिन: मुखर () संकलन-समय त्रुटि का कारण नहीं बनता है। यह उपरोक्त निर्माण का पूरा बिंदु है।
क्रिस पेसोजो

4
@GreweKokkor भोले मत बनो, लिनक्स एक व्यक्ति के लिए यह सब संभालने के लिए बहुत बड़ा है। लिनुस के पास अपने लेफ्टिनेंट हैं और उनके पास उनके हैं जो नीचे से बदलाव और सुधार को आगे बढ़ाते हैं। लिनस केवल यह तय करता है कि वह सुविधा चाहता है या नहीं, लेकिन वह कुछ हद तक सहकर्मियों पर भरोसा करता है। यदि आप इस बारे में अधिक जानना चाहते हैं कि खुले स्रोत के वातावरण में वितरित सिस्टम कैसे काम करता है, तो youtube वीडियो देखें: youtube.com/watch?v=4XpnKHJAok8 (यह बहुत ही दिलचस्प बात है)।
टॉमस प्रूजिना

3
@cpcloud, sizeofकेवल मूल्य का नहीं, "प्रकार" का मूल्यांकन करता है। इसका प्रकार इस मामले में अमान्य है।
विंस्टन इर्वर्ट

जवाबों:


1691

यह वास्तव में, यह जांचने का एक तरीका है कि क्या अभिव्यक्ति ई का मूल्यांकन 0 किया जा सकता है, और यदि नहीं, तो निर्माण को विफल करने के लिए

मैक्रो कुछ हद तक गलत है; इसके BUILD_BUG_OR_ZEROबजाय कुछ और अधिक होना चाहिए ...ON_ZERO। ( इस बारे में कभी-कभार चर्चा हुई है कि क्या यह एक भ्रमित नाम है ।)

आपको अभिव्यक्ति को इस तरह पढ़ना चाहिए:

sizeof(struct { int: -!!(e); }))
  1. (e): कम्प्यूट अभिव्यक्ति e

  2. !!(e): तार्किक रूप से दो बार नकारात्मक: 0यदि e == 0; अन्यथा 1

  3. -!!(e): चरण 2 से संख्यात्मक रूप से अभिव्यक्ति को नकारें: 0यदि यह था 0; अन्यथा -1

  4. struct{int: -!!(0);} --> struct{int: 0;}: यदि यह शून्य था, तो हम एक गुमनाम पूर्णांक बिटफील्ड के साथ एक संरचना की घोषणा करते हैं जिसमें चौड़ाई शून्य होती है। सब कुछ ठीक है और हम सामान्य रूप से आगे बढ़ते हैं।

  5. struct{int: -!!(1);} --> struct{int: -1;}: दूसरी ओर, यदि यह शून्य नहीं है, तो यह कुछ नकारात्मक संख्या होगी। किसी भी बिटफील्ड को नकारात्मक चौड़ाई के साथ घोषित करना एक संकलन त्रुटि है।

तो हम या तो एक बिटफील्ड के साथ हवा लेंगे जिसकी संरचना में चौड़ाई 0 है, जो ठीक है, या नकारात्मक चौड़ाई वाला बिटफील्ड है, जो एक संकलन त्रुटि है। फिर हम sizeofउस क्षेत्र को लेते हैं , इसलिए हमें size_tउपयुक्त चौड़ाई मिलती है (जो उस स्थिति में शून्य होगी जहां eशून्य है)।


कुछ लोगों ने पूछा: क्यों नहीं बस एक का उपयोग करें assert?

यहाँ कीथ्मो के जवाब की अच्छी प्रतिक्रिया है:

ये मैक्रोज़ एक संकलन-समय परीक्षण को लागू करते हैं, जबकि मुखर () एक रन-टाइम परीक्षण है।

बिल्कुल सही। आप अपने कर्नेल में उन समस्याओं का पता लगाना नहीं चाहते जो पहले पकड़ी जा सकती थीं! यह ऑपरेटिंग सिस्टम का एक महत्वपूर्ण टुकड़ा है। संकलन के समय कुछ हद तक समस्याओं का पता लगाया जा सकता है, इसलिए बेहतर है।


5
@ पश्चिमी स्थानों के बहुत सारे। अपने आप को देखो!
जॉन फेमेनेला

166
C ++ या C मानकों के हालिया वेरिएंट में static_assertसंबंधित उद्देश्यों के लिए कुछ ऐसा है।
बेसिल स्टायरनेविच 17

54
@Lundin - #error को कोड की 3 लाइनों का उपयोग करना होगा # if / # error / # endif, और केवल पूर्व-प्रोसेसर के लिए सुलभ मूल्यांकन के लिए काम करेगा। यह हैक कंपाइलर के लिए सुलभ किसी भी मूल्यांकन के लिए काम करता है।
एड स्टॉब

236
लिनक्स कर्नेल C ++ का उपयोग नहीं करता है, कम से कम नहीं जबकि लिनुस अभी भी जीवित है।
मार्क रैनसम

6
@ Dolda2000: " सी में बूलियन अभिव्यक्तियों को हमेशा शून्य या एक का मूल्यांकन करने के लिए परिभाषित किया जाता है " - बिल्कुल नहीं। ऑपरेटरों कि उपज "तार्किक बूलियन" परिणाम ( !, <, >, <=, >=, ==, !=, &&, ||) हमेशा 0 या 1. अन्य भाव उपज परिणाम है कि एक की स्थिति के रूप में इस्तेमाल किया जा सकता उपज सकता है, लेकिन केवल शून्य या गैर शून्य कर रहे हैं; उदाहरण के लिए, isdigit(c)जहां cएक अंक है, किसी भी गैर-शून्य मान प्राप्त कर सकते हैं (जो तब किसी स्थिति में सही माना जाता है)।
कीथ थॉम्पसन

256

:एक bitfield है। के रूप में !!, कि तार्किक डबल नकार है और इसलिए 0झूठे या 1सच्चे के लिए रिटर्न । और -एक ऋण चिन्ह है, जो अंकगणित का निषेध है।

यह सभी अवैध इनपुट पर कंपाइलर को प्राप्त करने के लिए सिर्फ एक चाल है।

विचार करें BUILD_BUG_ON_ZERO। जब -!!(e)एक नकारात्मक मूल्य का मूल्यांकन किया जाता है, जो एक संकलन त्रुटि पैदा करता है। अन्यथा -!!(e)0 का मूल्यांकन करता है, और 0 चौड़ाई के बिटफील्ड का आकार 0. है और इसलिए मैक्रो का size_tमान 0 के साथ है।

नाम मेरे विचार में कमजोर है क्योंकि इनपुट शून्य नहीं होने पर वास्तव में बिल्ड विफल हो जाता है।

BUILD_BUG_ON_NULLबहुत समान है, लेकिन एक के बजाय एक सूचक देता है int


14
है sizeof(struct { int:0; })कड़ाई अनुरूप?
ahउह

7
सामान्य रूप से परिणाम क्यों होगा 0? एक structही एक खाली bitfield, सच के साथ है, लेकिन मुझे नहीं लगता कि आकार 0 के साथ कि struct की अनुमति है। उदाहरण के लिए, यदि आप उस प्रकार का एक सरणी बनाते हैं, तो अलग-अलग सरणी तत्वों के पास अलग-अलग पते होने चाहिए, नहीं?
जेन्स गुस्तेद 15

2
वे वास्तव में परवाह नहीं करते हैं क्योंकि वे GNU एक्सटेंशन का उपयोग करते हैं, वे सख्त अलियासिंग नियम को अक्षम करते हैं और पूर्णांक को UB के रूप में नहीं मानते हैं। लेकिन मैं अगर यह सख्ती से सी अनुरूप है सोच रहा था
ouah

3
अनाम शून्य लंबाई बिटफ़िल्ड के बारे में @ उह, यहाँ देखें: stackoverflow.com/questions/4297095/…
डेविड

9
@DavidHeffernan सी वास्तव में सी को 0चौड़ाई के नामांकित क्षेत्र की अनुमति देता है , लेकिन अगर संरचना में कोई अन्य नामित सदस्य नहीं है। (C99, 6.7.2.1p2) "If the struct-declaration-list contains no named members, the behavior is undefined."उदाहरण के sizeof (struct {int a:1; int:0;})लिए कड़ाई से अनुरूप है, लेकिन sizeof(struct { int:0; })अपरिभाषित व्यवहार नहीं है।
१।

168

कुछ लोग इन मैक्रों के साथ भ्रमित होते दिख रहे हैं assert()

ये मैक्रोज़ एक संकलन-समय परीक्षण को लागू करते हैं, जबकि assert()एक रनटाइम परीक्षण है।


52

वैसे, मैं इस बात से काफी हैरान हूं कि इस सिंटैक्स के विकल्पों का उल्लेख नहीं किया गया है। एक अन्य सामान्य (लेकिन पुराना) तंत्र एक फ़ंक्शन को कॉल करने के लिए है जो परिभाषित नहीं है और यदि आपका दावा सही है तो फ़ंक्शन कॉल को संकलित करने के लिए ऑप्टिमाइज़र पर भरोसा करें।

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

हालांकि यह तंत्र काम करता है (जब तक अनुकूलन सक्षम होते हैं) इसमें लिंक न करने तक किसी त्रुटि की सूचना नहीं देने का नकारात्मक पक्ष है, जिस समय यह फ़ंक्शन के लिए परिभाषा खोजने में विफल रहता है you_did_something_bad ()। यही कारण है कि कर्नेल डेवलपर्स नकारात्मक आकार बिट फ़ील्ड चौड़ाई और नकारात्मक आकार सरणियों (बाद में जिनमें से जीसीसी 4.4 में ब्रेकिंग बिल्ड को रोक दिया) जैसी चाल का उपयोग करना शुरू कर दिया।

संकलन-समय की आवश्यकता के लिए सहानुभूति में, जीसीसी 4.3 ने errorफ़ंक्शन विशेषता को पेश किया जो आपको इस पुरानी अवधारणा पर विस्तार करने की अनुमति देता है, लेकिन आपके चयन के संदेश के साथ एक संकलन-समय त्रुटि उत्पन्न करता है - कोई और अधिक गुप्त "नकारात्मक" सरणी नहीं " त्रुटि संदेश!

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

वास्तव में, लिनक्स 3.9 के रूप में, अब हमारे पास एक मैक्रो है compiletime_assertजो इस सुविधा का उपयोग करता है और अधिकांश मैक्रोज़ को bug.hतदनुसार अपडेट किया गया है। फिर भी, इस मैक्रो का उपयोग इनिशियलाइज़र के रूप में नहीं किया जा सकता है। हालाँकि, स्टेटमेंट एक्सप्रेशन (एक और जीसीसी सी-एक्सटेंशन) का उपयोग करके , आप कर सकते हैं!

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

यह मैक्रो ठीक एक बार इसके पैरामीटर का मूल्यांकन करेगा (यदि इसके साइड-इफेक्ट्स हैं) और एक संकलन-समय त्रुटि पैदा करता है जो कहता है "मैंने आपको पांच नहीं देने के लिए कहा था!" यदि अभिव्यक्ति पांच का मूल्यांकन करती है या संकलन-समय स्थिर नहीं है।

तो हम नकारात्मक-आकार वाले बिट-फ़ील्ड के बजाय इसका उपयोग क्यों नहीं कर रहे हैं? काश, वर्तमान में स्टेटमेंट एक्सप्रेशन के उपयोग के कई प्रतिबंध हैं, जिनमें निरंतर इनिशियलाइज़र के रूप में उनका उपयोग (एनम कांस्टेंट के लिए, बिट-फील्ड चौड़ाई, आदि) शामिल हैं, भले ही स्टेटमेंट एक्सप्रेशन पूरी तरह से स्थिर हो (यानी, पूरी तरह से मूल्यांकन किया जा सकता है) संकलन-समय पर और अन्यथा __builtin_constant_p()परीक्षण पास करता है)। इसके अलावा, वे एक समारोह शरीर के बाहर इस्तेमाल नहीं किया जा सकता है।

उम्मीद है, जीसीसी जल्द ही इन कमियों में संशोधन करेगा और निरंतर बयान अभिव्यक्तियों को निरंतर शुरुआती के रूप में उपयोग करने की अनुमति देगा। यहाँ चुनौती भाषा विनिर्देश है जो एक कानूनी स्थिर अभिव्यक्ति है। C ++ 11 ने केवल इस प्रकार या चीज़ के लिए कॉन्स्ट्रेक्स कीवर्ड जोड़ा, लेकिन C11 में कोई भी प्रतिरूप मौजूद नहीं है। जबकि C11 को स्थैतिक अभिकथन मिला, जो इस समस्या का हिस्सा होगा, यह इन सभी कमियों को हल नहीं करेगा। इसलिए मुझे आशा है कि gcc एक विस्तार के रूप में एक constexpr कार्यक्षमता उपलब्ध कर सकता है -std = gnuc99 & -std = gnuc11 या कुछ इस तरह के और बयान अभिव्यक्ति एट पर इसके उपयोग की अनुमति देता है। अल।


6
आपके सभी समाधान वैकल्पिक नहीं हैं। मैक्रो के ऊपर की टिप्पणी बहुत स्पष्ट है " so the expression can be used e.g. in a structure initializer (or where-ever else comma expressions aren't permitted)." मैक्रो प्रकार की अभिव्यक्ति देता हैsize_t
विज

3
@Wiz हाँ, मुझे इसकी जानकारी है। शायद यह थोड़ा वर्बोज़ था और शायद मुझे अपने शब्दों को फिर से देखने की ज़रूरत है, लेकिन मेरी बात स्थिर कथनों के लिए विभिन्न तंत्रों का पता लगाने और यह दिखाने के लिए थी कि हम अभी भी नकारात्मक आकार के बिटफ़िल्ड का उपयोग क्यों कर रहे हैं। संक्षेप में, यदि हमें निरंतर कथन अभिव्यक्ति के लिए एक तंत्र मिलता है, तो हमारे पास अन्य विकल्प खुले रहेंगे।
डैनियल सैंटोस

वैसे भी हम एक चर के लिए इन मैक्रो का उपयोग नहीं कर सकते। सही? error: bit-field ‘<anonymous>’ width not an integer constantइसकी अनुमति केवल स्थिरांक है। तो, क्या फायदा है?
कार्तिक राज पलानीचामी

1
@ कार्तिक यह जानने के लिए लिनक्स कर्नेल के स्रोतों को खोजें कि इसका उपयोग क्यों किया जाता है।
डैनियल सैंटोस

@ सुपरकैट मैं यह नहीं देखता कि आपकी टिप्पणी किस तरह से संबंधित है। क्या आप कृपया इसे संशोधित कर सकते हैं, बेहतर समझा सकते हैं कि आपका क्या मतलब है या इसे हटा दें?
डैनियल सैंटोस

36

0यदि स्थिति झूठी है, लेकिन यह साइज़ बिटफ़िल्ड बना रहा है, लेकिन यदि स्थिति सही / गैर-शून्य है तो एक साइज़ -1( -!!1) बिटफ़ील्ड। पूर्व के मामले में, कोई त्रुटि नहीं है और एक प्रारंभिक सदस्य के साथ संरचना को प्रारंभ किया गया है। बाद के मामले में, एक संकलित त्रुटि है (और आकार -1बिटफ़ील्ड जैसी कोई चीज नहीं बनाई जाती है, निश्चित रूप से)।


3
वास्तव में यह size_tमान 0 के साथ वापस आ रहा है यदि स्थिति सही है।
डेविड हेफर्नन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.