शून्य तत्वों के साथ सरणी की आवश्यकता क्या है?


122

लिनक्स कर्नेल कोड में मुझे निम्नलिखित चीज़ मिली जो मैं समझ नहीं पा रहा हूँ।

 struct bts_action {
         u16 type;
         u16 size;
         u8 data[0];
 } __attribute__ ((packed));

कोड यहाँ है: http://lxr.free-electrons.com/source/include/linux/ti_wilink_st.h

शून्य तत्वों वाले डेटा की एक सरणी की आवश्यकता और उद्देश्य क्या है?


मुझे यकीन नहीं है कि या तो एक शून्य-लंबाई-सरणियों या संरचना-हैक टैग होना चाहिए ...
हिप्पिट्रैएल

@ ह्पीपिट्राईल, क्योंकि अक्सर जब कोई पूछता है कि यह संरचना क्या है, तो वे नहीं जानते कि इसे "लचीला सरणी सदस्य" कहा जाता है। अगर वे करते, तो वे आसानी से अपना जवाब पा सकते थे। चूंकि वे नहीं करते हैं, इसलिए वे इस प्रश्न को टैग नहीं कर सकते। यही कारण है कि हमारे पास ऐसा कोई टैग नहीं है।
शहबाज

10
फिर से मतदान करें। मैं मानता हूं कि यह कोई डुप्लिकेट नहीं था, क्योंकि अन्य पदों में से कोई भी शून्य लंबाई के साथ एक गैर-मानक "स्ट्रक्चर हैक" के संयोजन को संबोधित नहीं करता है और अच्छी तरह से परिभाषित C99 सुविधा लचीला सरणी सदस्य है। मुझे यह भी लगता है कि सी प्रोग्रामिंग समुदाय के लिए लिनक्स कर्नेल से किसी भी अस्पष्ट कोड पर कुछ प्रकाश डालना हमेशा के लिए लाभकारी होता है। मुख्य रूप से कई लोगों की धारणा है कि अज्ञात कारणों से लिनक्स कर्नेल कुछ प्रकार की आर्ट सी कोड की स्थिति है। जबकि वास्तव में यह गैर-मानक कारनामों से भरी भयानक गंदगी है जिसे कभी भी कुछ सी कैनन के रूप में नहीं माना जाना चाहिए।
लंडिन

5
डुप्लिकेट नहीं - पहली बार नहीं है जब मैंने किसी को अनावश्यक रूप से एक प्रश्न के करीब देखा है। इसके अलावा, मुझे लगता है कि यह सवाल SO नॉलेज बेस से जुड़ता है।
अनिकेत इंग

जवाबों:


139

यह दो बार malloc( kmallocइस मामले में) कॉल किए बिना, डेटा के चर आकार का एक तरीका है । आप इसे इस तरह उपयोग करेंगे:

struct bts_action *var = kmalloc(sizeof(*var) + extra, GFP_KERNEL);

यह मानक नहीं हुआ करता था और इसे हैक माना जाता था (जैसा कि अनिकेत ने कहा था), लेकिन इसे C99 में मानकीकृत किया गया था । अब इसके लिए मानक प्रारूप है:

struct bts_action {
     u16 type;
     u16 size;
     u8 data[];
} __attribute__ ((packed)); /* Note: the __attribute__ is irrelevant here */

ध्यान दें कि आप dataक्षेत्र के लिए किसी भी आकार का उल्लेख नहीं करते हैं । ध्यान दें कि यह विशेष चर केवल संरचना के अंत में आ सकता है।


C99 में, इस मामले को 6.7.2.1.16 (जोर मेरा) में समझाया गया है:

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

या दूसरे शब्दों में, यदि आपके पास:

struct something
{
    /* other variables */
    char data[];
}

struct something *var = malloc(sizeof(*var) + extra);

आप var->dataसूचकांक में प्रवेश कर सकते हैं [0, extra)। ध्यान दें कि sizeof(struct something)केवल अन्य चरों के लिए आकार का लेखा-जोखा देगा, अर्थात data0 का आकार देगा।


यह भी ध्यान रखना दिलचस्प हो सकता है कि मानक वास्तव में mallocइस तरह के एक निर्माण का उदाहरण देता है (6.7.2.1.17):

struct s { int n; double d[]; };

int m = /* some value */;
struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));

उसी स्थान पर मानक द्वारा एक और दिलचस्प नोट है (जोर मेरा):

यह मानते हुए कि मॉलोक में कॉल सफल होता है, पी द्वारा बताई गई वस्तु, अधिकांश उद्देश्यों के लिए, जैसे कि पी घोषित किया गया था:

struct { int n; double d[m]; } *p;

(ऐसी परिस्थितियां हैं जिनमें यह समानता टूट गई है, विशेष रूप से, सदस्य d के ऑफसेट समान नहीं हो सकते हैं )।


स्पष्ट होने के लिए, प्रश्न में मूल कोड अभी भी C99 (न ही C11) में मानक नहीं है, और फिर भी इसे हैक माना जाएगा। C99 मानकीकरण को बाउंड बाउंड को छोड़ना होगा।
MM

क्या है [0, extra)?
एसएस ऐनी


36

यह वास्तव में जीसीसी ( C90 ) के लिए एक हैक है।

इसे स्ट्रक्चर हैक भी कहा जाता है ।

तो अगली बार, मैं कहूंगा:

struct bts_action *bts = malloc(sizeof(struct bts_action) + sizeof(char)*100);

यह कहने के बराबर होगा:

struct bts_action{
    u16 type;
    u16 size;
    u8 data[100];
};

और मैं इस तरह की संरचना की किसी भी संख्या को बना सकता हूं।


7

विचार संरचना के अंत में एक चर-आकार की सरणी के लिए अनुमति देने के लिए है। संभवतः, bts_actionएक निश्चित आकार के हेडर ( typeऔर sizeफ़ील्ड्स) और चर-आकार के dataसदस्य के साथ कुछ डेटा पैकेट है । इसे 0-लंबाई के सरणी के रूप में घोषित करके, इसे किसी अन्य सरणी की तरह अनुक्रमित किया जा सकता है। आप bts_action1024 के बाइट के dataआकार का एक ढांचा आवंटित करेंगे , जैसे:

size_t size = 1024;
struct bts_action* action = (struct bts_action*)malloc(sizeof(struct bts_action) + size);

इसे भी देखें: http://c2.com/cgi/wiki?StructHack


2
@Aniket: मुझे पूरी तरह से यकीन नहीं है कि यह विचार कहां से आता है
17

C ++ में हां, C में, जरूरत नहीं।
एएमसी

2
@ षेउ, यह इस तथ्य से आता है कि आपकी लेखन शैली mallocआपको कई बार खुद को दोहराती है और यदि कभी किसी प्रकार का actionपरिवर्तन होता है, तो आपको इसे कई बार ठीक करना होगा। अपने लिए निम्नलिखित दो की तुलना करें और आपको पता चल जाएगा: struct some_thing *variable = (struct some_thing *)malloc(10 * sizeof(struct some_thing));बनाम struct some_thing *variable = malloc(10 * sizeof(*variable));दूसरा छोटा है, साफ है और स्पष्ट रूप से बदलना आसान है।
शाहबाज़

5

कोड मान्य नहीं है C (इसे देखें )। लिनक्स कर्नेल स्पष्ट कारणों के लिए है, पोर्टेबिलिटी से संबंधित नहीं है, इसलिए यह गैर-मानक कोड का उपयोग करता है।

वे जो कर रहे हैं वह सरणी आकार 0. के साथ एक जीसीसी गैर-मानक निष्कर्षण है। एक मानक अनुपालन कार्यक्रम लिखा u8 data[];होगा और इसका मतलब बहुत ही समान होगा। लिनक्स कर्नेल के लेखक स्पष्ट रूप से चीजों को अनावश्यक रूप से जटिल और गैर-मानक बनाना पसंद करते हैं, अगर ऐसा करने का विकल्प खुद को प्रकट करता है।

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

जीसीसी ने इसे अपरिभाषित से अच्छी तरह से परिभाषित व्यवहार में बदलने के लिए एक गैर-मानक विस्तार किया। C99 मानक ने तब इस अवधारणा को अनुकूलित किया और कोई भी आधुनिक C प्रोग्राम इसलिए बिना जोखिम के इस सुविधा का उपयोग कर सकता है। इसे C99 / C11 में लचीले सरणी सदस्य के रूप में जाना जाता है ।


3
मुझे संदेह है कि "लिनक्स कर्नेल पोर्टेबिलिटी से चिंतित नहीं है"। शायद आप अन्य संकलक के लिए पोर्टेबिलिटी का मतलब है? यह सच है कि यह जीसीसी की विशेषताओं के साथ काफी रोमांचित है।
शाहबाज

3
फिर भी, मुझे लगता है कि यह विशेष कोड कोड मुख्यधारा का कोड नहीं है और शायद इसलिए छोड़ दिया जाता है क्योंकि इसके लेखक ने इस पर ज्यादा ध्यान नहीं दिया। लाइसेंस कुछ टेक्सास इंस्ट्रूमेंट्स ड्राइवरों के बारे में कहता है, इसलिए इसकी संभावना नहीं है कि कर्नेल के मुख्य प्रोग्रामर ने इस पर कोई ध्यान नहीं दिया। मुझे पूरा यकीन है कि कर्नेल डेवलपर्स नए मानकों या नए अनुकूलन के अनुसार पुराने कोड को लगातार अपडेट कर रहे हैं। यह सुनिश्चित करने के लिए बहुत बड़ा है कि सब कुछ अपडेट हो गया है!
शहबाज

1
@ शहबाज "स्पष्ट" भाग के साथ, मेरा मतलब अन्य ऑपरेटिव सिस्टम के लिए पोर्टेबिलिटी है, जो स्वाभाविक रूप से कोई मतलब नहीं होगा। लेकिन वे अन्य कंपाइलरों के लिए पोर्टेबिलिटी के बारे में ध्यान नहीं दे रहे हैं, उन्होंने कई GCC एक्सटेंशन का उपयोग किया है कि लिनक्स संभवतः किसी अन्य कंपाइलर को पोर्ट नहीं करेगा।
लुंडिन

3
@Shabaz टेक्सास इंस्ट्रूमेंट्स लेबल वाली किसी भी चीज़ के मामले के लिए, TI खुद ही सबसे बेकार, भद्दे, भोले सी कोड को देखने के लिए कुख्यात हैं, विभिन्न TI चिप्स के लिए उनके ऐप नोटों में। यदि कोड TI से उत्पन्न होता है, तो इससे उपयोगी कुछ की व्याख्या करने के अवसर के बारे में सभी शर्त बंद हैं।
लुंडिन

4
यह सच है कि linux और gcc अविभाज्य हैं। लिनक्स कर्नेल को समझना भी काफी कठिन है (अधिकतर क्योंकि OS वैसे भी जटिल होता है)। मेरी बात हालांकि, यह कहना अच्छा नहीं था कि "लिनक्स कर्नेल के लेखक स्पष्ट रूप से चीजों को अनावश्यक रूप से जटिल और गैर-मानक बनाना पसंद करते हैं, अगर ऐसा करने का विकल्प तीसरे पक्ष-ईश खराब कोडिंग अभ्यास के कारण" खुद को प्रकट करता है। ।
शहबाज

1

शून्य लंबाई सरणी का एक अन्य उपयोग एक संकलित नाम के रूप में एक संरचना के अंदर एक संकलित समय संरचना ऑफसेट चेक की सहायता के लिए है।

मान लें कि आपके पास कुछ बड़ी संरचनात्मक परिभाषाएँ हैं (कई कैश लाइनें फैलाते हैं) जो आप यह सुनिश्चित करना चाहते हैं कि वे शुरुआत में और बीच में जहां यह सीमा पार करती हैं, दोनों जगह कैश लाइन की सीमा से जुड़ी होती हैं।

struct example_large_s
{
    u32 first; // align to CL
    u32 data;
    ....
    u64 *second;  // align to second CL after the first one
    ....
};

कोड में आप उन्हें GCC एक्सटेंशन का उपयोग करके घोषित कर सकते हैं जैसे:

__attribute__((aligned(CACHE_LINE_BYTES)))

लेकिन आप अभी भी यह सुनिश्चित करना चाहते हैं कि यह रनटाइम में लागू हो।

ASSERT (offsetof (example_large_s, first) == 0);
ASSERT (offsetof (example_large_s, second) == CACHE_LINE_BYTES);

यह एकल संरचना के लिए काम करेगा, लेकिन कई संरचनाओं को कवर करना कठिन होगा, प्रत्येक में अलग-अलग सदस्य का नाम होना चाहिए। आपको सबसे नीचे कोड प्राप्त करने की संभावना होगी जहां आपको प्रत्येक संरचना के पहले सदस्य के नाम खोजने होंगे:

assert (offsetof (one_struct,     <name_of_first_member>) == 0);
assert (offsetof (one_struct,     <name_of_second_member>) == CACHE_LINE_BYTES);
assert (offsetof (another_struct, <name_of_first_member>) == 0);
assert (offsetof (another_struct, <name_of_second_member>) == CACHE_LINE_BYTES);

इस तरह से जाने के बजाय, आप संरचना में एक शून्य लंबाई सरणी को एक संगत नाम के साथ नामित लेबल के रूप में घोषित कर सकते हैं लेकिन किसी भी स्थान का उपभोग नहीं करते हैं।

#define CACHE_LINE_ALIGN_MARK(mark) u8 mark[0] __attribute__((aligned(CACHE_LINE_BYTES)))
struct example_large_s
{
    CACHE_LINE_ALIGN_MARK (cacheline0);
    u32 first; // align to CL
    u32 data;
    ....
    CACHE_LINE_ALIGN_MARK (cacheline1);
    u64 *second;  // align to second CL after the first one
    ....
};

फिर रनटाइम एश्योरेंस कोड बनाए रखना बहुत आसान होगा:

assert (offsetof (one_struct,     cacheline0) == 0);
assert (offsetof (one_struct,     cacheline1) == CACHE_LINE_BYTES);
assert (offsetof (another_struct, cacheline0) == 0);
assert (offsetof (another_struct, cacheline1) == CACHE_LINE_BYTES);

दिलचस्प विचार। बस ध्यान दें कि मानक द्वारा 0-लंबाई सरणियों की अनुमति नहीं है, इसलिए यह एक संकलक-विशिष्ट चीज है। इसके अलावा, एक परिभाषा में 0-लंबाई सरणियों के व्यवहार की gcc की परिभाषा को उद्धृत करना एक अच्छा विचार हो सकता है, बहुत कम से कम यह दिखाने के लिए कि क्या यह घोषणा से पहले या बाद में पैडिंग पेश कर सकता है।
शहबाज
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.