साइज़ोफ़ () का उपयोग किए बिना कोड का यह टुकड़ा सरणी आकार कैसे निर्धारित करता है?


134

कुछ सी साक्षात्कार प्रश्नों के माध्यम से जा रहे हैं, मैंने एक प्रश्न पाया है "साइलोऑफ़ ऑपरेटर का उपयोग किए बिना सी में एक सरणी का आकार कैसे ढूंढें?", निम्न समाधान के साथ। यह काम करता है, लेकिन मुझे समझ नहीं आता कि क्यों।

#include <stdio.h>

int main() {
    int a[] = {100, 200, 300, 400, 500};
    int size = 0;

    size = *(&a + 1) - a;
    printf("%d\n", size);

    return 0;
}

जैसा कि अपेक्षित था, यह 5 रिटर्न देता है।

संपादित करें: लोगों ने इस उत्तर को इंगित किया , लेकिन वाक्यविन्यास थोड़ा अलग है, अर्थात अनुक्रमण विधि

size = (&arr)[1] - arr;

इसलिए मेरा मानना ​​है कि दोनों प्रश्न मान्य हैं और समस्या के लिए थोड़ा अलग दृष्टिकोण है। अपार मदद और पूरी तरह से स्पष्टीकरण के लिए आप सभी का धन्यवाद!


13
खैर, यह नहीं मिल सकता है, लेकिन ऐसा लगता है कि यह सख्ती से बोल रहा है। अनुलग्नक J.2 स्पष्ट रूप से कह रहा है: यूनिरी के ऑपरेटर * ऑपरेटर का एक अमान्य मूल्य है जो एक अपरिभाषित व्यवहार है। यहां &a + 1किसी भी मान्य वस्तु की ओर इशारा नहीं किया गया है, इसलिए यह अमान्य है।
यूजीन श।



@AlmaDo अच्छी तरह से वाक्यविन्यास थोड़ा अलग है, यानी अनुक्रमण भाग, इसलिए मेरा मानना ​​है कि यह प्रश्न अभी भी अपने आप में मान्य है, लेकिन मैं गलत हो सकता हूं। यह इंगित करने के लिए धन्यवाद!
जोंजिलिक

1
@janojlicz वे अनिवार्य रूप से एक ही हैं, क्योंकि (ptr)[x]जैसा है वैसा ही है *((ptr) + x)
एसएस ऐनी

जवाबों:


135

जब आप एक पॉइंटर में 1 जोड़ते हैं, तो परिणाम पॉइंट-टू टाइप की वस्तुओं (यानी, एक सरणी) के अनुक्रम के अगले ऑब्जेक्ट का स्थान होता है। यदि pकिसी intवस्तु p + 1को इंगित करता है , तो अगले intको एक क्रम में इंगित करेगा । अगर pसे 5-तत्व सरणी के लिए अंक int(इस मामले में, अभिव्यक्ति &a), तो p + 1अगले को इंगित करेंगे के 5-तत्व वाली सरणीint एक क्रम में।

दो बिंदुओं को घटाना (बशर्ते वे दोनों एक ही सरणी ऑब्जेक्ट में इंगित करते हैं, या कोई एक सरणी के अंतिम तत्व को इंगित कर रहा है) उन दो बिंदुओं के बीच ऑब्जेक्ट (सरणी तत्व) की संख्या पैदा करता है।

अभिव्यक्ति &aके पते की पैदावार होती है a, और इसका प्रकार int (*)[5](पॉइंटर से 5-तत्व सरणी int) होता है। अभिव्यक्ति &a + 1अगले 5-तत्व सरणी के पते का intपालन ​​करती है a, और इसके प्रकार भी हैं int (*)[5]। अभिव्यक्ति *(&a + 1)का परिणाम है &a + 1, जैसे कि यह पहले intके अंतिम तत्व के बाद का पता देता है a, और इसका प्रकार होता है int [5], जो इस संदर्भ में "अभिव्यक्ति" के प्रकार की अभिव्यक्ति करता है int *

इसी प्रकार, aसरणी के पहले तत्व के लिए एक सूचक " अभिव्यक्ति " होता है और प्रकार होता है int *

एक चित्र मदद कर सकता है:

int [5]  int (*)[5]     int      int *

+---+                   +---+
|   | <- &a             |   | <- a
| - |                   +---+
|   |                   |   | <- a + 1
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
+---+                   +---+
|   | <- &a + 1         |   | <- *(&a + 1)
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
+---+                   +---+

यह एक ही भंडारण के दो दृश्य हैं - बाईं ओर, हम इसे 5-तत्व सरणियों के अनुक्रम के रूप में देख रहे हैं int, जबकि दाईं ओर, हम इसे एक अनुक्रम के रूप में देख रहे हैं int। मैं विभिन्न भाव और उनके प्रकार भी दिखाता हूं।

ज्ञात हो, अभिव्यक्ति अपरिभाषित व्यवहार का*(&a + 1) परिणाम है :

...
यदि परिणाम सरणी ऑब्जेक्ट के अंतिम तत्व के पिछले एक को इंगित करता है, तो इसका उपयोग मूल्यांकन किए जाने वाले एक * * ऑपरेटर के ऑपरेटर के रूप में नहीं किया जाएगा।

सी 2011 ऑनलाइन ड्राफ्ट , 6.5.6 / 9


13
वह "उपयोग नहीं किया जाएगा" पाठ आधिकारिक है: C 2018 6.5.6 8.
एरिक पोस्टपिसिल

@EricPostpischil: क्या आपके पास 2018 प्री-पब ड्राफ्ट (N1570.pdf के समान) का लिंक है?
जॉन बोडे

1
@ जॉनबोड: इस उत्तर में वेबैक मशीन का लिंक है । मैंने अपनी खरीदी गई कॉपी में आधिकारिक मानक की जाँच की।
एरिक पोस्टपिसिल

7
तो अगर किसी ने लिखा है कि size = (int*)(&a + 1) - a;यह कोड पूरी तरह से मान्य होगा? : ओ
Gizmo

@Gizmo शायद वे मूल रूप से नहीं लिखा था क्योंकि उस तरह से आपको तत्व प्रकार निर्दिष्ट करना होगा; मूल को संभवतः विभिन्न तत्व प्रकारों पर टाइप-जेनेरिक उपयोग के लिए मैक्रो के रूप में परिभाषित किया गया था।
लेउशेंको

35

यह पंक्ति सबसे अधिक महत्वपूर्ण है:

size = *(&a + 1) - a;

जैसा कि आप देख सकते हैं, यह सबसे पहले इसका पता लेता है aऔर इसमें एक जोड़ता है। उसके बाद, यह dereferences कि सूचक और इसके मूल मूल्य को घटाता aहै।

सी में सूचक अंकगणित इसके कारण सरणी में तत्वों की संख्या को वापस करने का कारण बनता है, या 5। एक जोड़ना और &a5 intएस के बाद के अगले सरणी के लिए एक सूचक है a। उसके बाद, यह कोड परिणामी सूचक को घटाता है और उससे घटाता है a(एक सरणी प्रकार जो उस सूचक को क्षय हो जाता है), उस सरणी में तत्वों की संख्या देता है।

सूचक अंकगणितीय कैसे काम करता है पर विवरण:

मान लें कि आपके पास एक संकेतक है xyzजो एक intप्रकार को इंगित करता है और इसमें मूल्य शामिल है (int *)160। जब आप किसी संख्या को घटाते हैं xyz, तो C यह निर्दिष्ट करता है कि उस से घटाई गई वास्तविक राशि xyzउस संख्या के आकार का है जो वह इंगित करता है। उदाहरण के लिए, यदि आप से घटाए 5गए हैं xyz, तो xyzपरिणाम का मूल्य होगा xyz - (sizeof(*xyz) * 5)यदि सूचक अंकगणितीय लागू नहीं होता है।

जैसा aकि एक 5 intप्रकार है, परिणामी मान 5. होगा। हालांकि, यह एक सूचक के साथ काम नहीं करेगा, केवल एक सरणी के साथ। यदि आप एक पॉइंटर के साथ यह कोशिश करते हैं, तो परिणाम हमेशा रहेगा 1

यहाँ एक छोटा सा उदाहरण है जो पते दिखाता है और यह कैसे अपरिभाषित है। बाईं ओर के पते पते दिखाते हैं:

a + 0 | [a[0]] | &a points to this
a + 1 | [a[1]]
a + 2 | [a[2]]
a + 3 | [a[3]]
a + 4 | [a[4]] | end of array
a + 5 | [a[5]] | &a+1 points to this; accessing past array when dereferenced

इसका मतलब है कि कोड (या ) aसे घटा रहा है, दे रहा है ।&a[5]a+55

ध्यान दें कि यह अपरिभाषित व्यवहार है, और इसका उपयोग किसी भी परिस्थिति में नहीं किया जाना चाहिए। सभी प्लेटफार्मों के अनुरूप होने के व्यवहार की अपेक्षा न करें और उत्पादन कार्यक्रमों में इसका उपयोग न करें।


27

हम्म, मुझे संदेह है कि यह कुछ ऐसा है जो सी के शुरुआती दिनों में वापस काम नहीं करेगा। हालांकि यह चतुर है।

एक समय में एक कदम उठाना:

  • &a एक int प्रकार की वस्तु को पॉइंटर मिलता है [5]
  • +1 अगली ऐसी वस्तु प्राप्त होती है जो मानती है कि उनमें से एक सरणी है
  • * प्रभावी ढंग से उस पते को int को टाइप पॉइंटर में परिवर्तित करता है
  • -a दो अंतर बिंदुओं को घटाता है, उनके बीच अंतर उदाहरणों की गिनती लौटाता है।

मुझे यकीन नहीं है कि यह पूरी तरह से कानूनी है (इसमें मेरा मतलब है भाषा-वकील कानूनी - व्यवहार में यह काम नहीं करेगा), जिस पर कुछ प्रकार के संचालन दिए गए हैं। उदाहरण के लिए आपको केवल "अनुमत" दो बिंदुओं को घटाना है जब वे एक ही सरणी में तत्वों को इंगित करते हैं। *(&a+1)एक अन्य सरणी तक पहुँचने के द्वारा संश्लेषित किया गया था, जो कि एक मूल सरणी है, इसलिए वास्तव में उसी सरणी में एक सूचक नहीं है a। इसके अलावा, जब आपको किसी सूचक को किसी सरणी के अंतिम तत्व के साथ संश्लेषित करने की अनुमति दी जाती है, और आप किसी भी वस्तु को 1 तत्व के सरणी के रूप में मान सकते हैं, तो *इस संश्लेषित सूचक पर dereferencing ( ) को "अनुमति" नहीं दी जाती है, भले ही यह इस मामले में कोई व्यवहार नहीं है!

मुझे संदेह है कि सी (के एंड आर सिंटैक्स, किसी के?) के शुरुआती दिनों में, एक सरणी एक सूचक में बहुत अधिक जल्दी से क्षय हो जाती है, इसलिए *(&a+1)केवल टाइप के अगले पॉइंटर का पता वापस हो सकता है int **। आधुनिक सी ++ की अधिक कठोर परिभाषाएं निश्चित रूप से सूचक को सरणी के प्रकार को मौजूद करने और सरणी आकार जानने की अनुमति देती हैं, और शायद सी मानकों ने सूट का पालन किया है। सभी सी फ़ंक्शन कोड केवल संकेत के रूप में संकेत लेते हैं, इसलिए तकनीकी दृश्यमान अंतर न्यूनतम है। लेकिन मैं यहां केवल अनुमान लगा रहा हूं।

इस तरह का विस्तृत कानूनी प्रश्न आमतौर पर संकलित कोड के बजाय सी इंटरप्रेटर या एक प्रकार का उपकरण पर लागू होता है। एक व्याख्याकार सरणियों के एक सरणी के रूप में 2 डी सरणी को लागू कर सकता है, क्योंकि लागू करने के लिए एक कम रनटाइम सुविधा है, जिस स्थिति में +1 को निष्क्रिय करना घातक होगा, और यहां तक ​​कि अगर यह काम करता है तो गलत उत्तर देगा।

एक और संभावित कमजोरी यह हो सकती है कि सी कंपाइलर बाहरी सरणी को संरेखित कर सकता है। कल्पना कीजिए कि यह 5 वर्णों ( char arr[5]) की एक सरणी थी , जब कार्यक्रम करता है तो &a+1यह "सरणी का सरणी" व्यवहार को लागू कर रहा है। कंपाइलर यह तय कर सकता है कि 5 वर्णों ( char arr[][5]) की सरणी का एक सरणी वास्तव में 8 वर्णों ( char arr[][8]) के सरणी के रूप में उत्पन्न होता है , ताकि बाहरी सरणी अच्छी तरह से संरेखित हो। जिस कोड की हम चर्चा कर रहे हैं, वह अब सरणी आकार को 8 के रूप में रिपोर्ट करेगा, 5 नहीं। मैं यह नहीं कह रहा हूं कि एक विशेष संकलक निश्चित रूप से ऐसा करेगा, लेकिन यह हो सकता है।


काफी उचित। हालांकि समझाने के लिए कठिन कारणों के लिए, हर कोई आकार () / आकार () का उपयोग करता है?
जेम टेलर

5
ज्यादातर लोग करते हैं। उदाहरण के लिए, sizeof(array)/sizeof(array[0])किसी सरणी में तत्वों की संख्या देता है।
एसएस ऐनी

सी संकलक को सरणी को संरेखित करने की अनुमति है, लेकिन मुझे ऐसा करने के लिए असंबंधित है कि ऐसा करने के बाद सरणी के प्रकार को बदलने की अनुमति है। गद्दी बाइट्स डालने से संरेखण अधिक वास्तविक रूप से लागू होगा।
केविन

1
पॉइंटर्स का घटाना केवल दो पॉइंटर्स को एक ही एरे में सीमित नहीं करता है - पॉइंटर्स को एरे के अंत में एक होने की अनुमति है। &a+1परिभषित किया। जैसा कि जॉन बोलिंगर ने नोट किया *(&a+1)है, क्योंकि यह एक ऐसी वस्तु का स्थगन करने का प्रयास करता है जो मौजूद नहीं है।
एरिक पोस्टपिसिल

5
एक संकलक एक के char [][5]रूप में लागू नहीं कर सकता char arr[][8]। एक सरणी बस इसमें दोहराई गई वस्तुएं हैं; कोई पैडिंग नहीं है। अतिरिक्त, यह सी 2018 6.5.3.4 7 में (गैर-मानक) उदाहरण 2 को तोड़ देगा, जो हमें बताता है कि हम एक सरणी में तत्वों की संख्या की गणना कर सकते हैं sizeof array / sizeof array[0]
एरिक पोस्टपिसिल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.