किसी संख्या के वर्ग की गणना करने के इस तरीके को समझ नहीं सकते


135

मुझे एक फ़ंक्शन मिला है जो किसी संख्या के वर्ग की गणना करता है:

int p(int n) {
    int a[n]; //works on C99 and above
    return (&a)[n] - a;
}

यह n 2 का मान लौटाता है । सवाल यह है कि यह कैसे करता है? एक छोटे से परीक्षण के बाद, मैंने पाया है कि दोनों के बीच (&a)[k]और (&a)[k+1]है sizeof(a)/ sizeof(int)। ऐसा क्यों है?


6
क्या आपके पास ऐसी जानकारी है जहां आपको यह जानकारी मिली है?
आर साहू

4
int p(n)? क्या वह भी संकलन करता है?
बराक मानोस

78
यह कमाल है, अब इसे फिर से उपयोग न करें और इसके बजाय n * n का उपयोग करें ...

26
या बेहतर:int q(int n) { return sizeof (char [n][n]); }
ouah

17
@ouah इस सवाल यह सोचते हैं को संदर्भित करता है codegolf.stackexchange.com/a/43262/967 कारण मैंने किया उपयोग नहीं sizeofवर्ण को बचाने के लिए किया गया था। बाकी सभी: यह जानबूझकर अस्पष्ट कोड है, यह अपरिभाषित व्यवहार है, @ ouah का उत्तर सही है।
इकतुर

जवाबों:


117

जाहिर है एक हैक ... लेकिन *ऑपरेटर का उपयोग किए बिना एक संख्या को चुकाने का एक तरीका (यह एक कोडिंग प्रतियोगिता की आवश्यकता थी)।

(&a)[n] 

intस्थान पर एक सूचक के बराबर है

(a + sizeof(a[n])*n)

और इस प्रकार पूरी अभिव्यक्ति है

  (&a)[n] -a 

= (a + sizeof(a[n])*n -a) /sizeof(int)

= sizeof(a[n])*n / sizeof(int)
= sizeof(int) * n * n / sizeof(int)
= n * n

11
और जैसा कि आप स्पष्ट रूप से कहते हैं, लेकिन मुझे स्पष्ट करने की आवश्यकता है, यह सबसे अच्छा एक वाक्यविन्यास हैक है। गुणा ऑपरेशन अभी भी वहीं होगा; यह सिर्फ ऑपरेटर है जिसे टाला जा रहा है।
टॉमी

मुझे वह मिल गया जो उसके पीछे होता है, लेकिन मेरा असली सवाल यही है (& a) [k] एक ही पते पर + k * sizeof (a) / sizeof (int)
Emanuel

33
एक पुराने कोडर के रूप में मुझे इस तथ्य से घृणा होती है कि संकलक को (&a)सूचक के रूप में माना जा सकता है कि संकलन समय पर n*sizeof(int)कब nनहीं जाना जाता है। C एक सरल भाषा हुआ करती थी ...
Floris

यह एक बहुत ही चतुर हैक है, लेकिन कुछ ऐसा जो आप उत्पादन कोड (उम्मीद) में नहीं देख रहे होंगे।
जॉन ओडोम

14
एक तरफ के रूप में, यह भी यूबी है, क्योंकि यह न तो अंतर्निहित सरणी के एक तत्व को इंगित करने के लिए एक संकेतक बढ़ाता है, न ही सिर्फ अतीत।
डिडुप्लिकेटर

86

इस हैक को समझने के लिए, पहले आपको सूचक अंतर को समझने की आवश्यकता है, अर्थात, तब क्या होता है जब एक ही सरणी के तत्वों को इंगित करने वाले दो संकेत घटाए जाते हैं?

जब एक सूचक दूसरे से घटाया जाता है, तो परिणाम बिंदुओं के बीच की दूरी (सरणी तत्वों में मापा जाता है) होता है। तो, अगर pइंगित करता है a[i]और qकरने के लिए इंगित करता है a[j], तो p - qबराबर हैi - j

C11: 6.5.6 एडिटिव ऑपरेटर (p9):

जब दो बिंदुओं को घटाया जाता है , तो दोनों एक ही एरे ऑब्जेक्ट के तत्वों को इंगित करेंगे, या एरे ऑब्जेक्ट के अंतिम तत्व को एक अतीत; परिणाम दो सरणी तत्वों की सदस्यता का अंतर है । [...]।
दूसरे शब्दों में, यदि एरे ऑब्जेक्ट के क्रमशः, -थ और -थ एलिमेंट्स , एक्सप्रेशन Pऔर Qपॉइंट टू, एक्सप्रेशन में वैल्यू है , तो यह वैल्यू टाइप के ऑब्जेक्ट में फिट बैठता है ।ij(P)-(Q)i−jptrdiff_t

अब मैं उम्मीद कर रहा हूं कि आप सूचक को सरणी नाम रूपांतरण के बारे में जानते हैं, aसूचक को सरणी के पहले तत्व में कनवर्ट करता है a&aपूरे मेमोरी ब्लॉक का पता है, अर्थात यह सरणी का एक पता है a। नीचे दिया गया आंकड़ा आपको समझने में मदद करेगा ( विस्तृत विवरण के लिए इस उत्तर को पढ़ें ):

यहां छवि विवरण दर्ज करें

इससे आपको यह समझने में मदद मिलेगी कि क्यों a और &aएक ही पता है और कैसे (&a)[i]पता है कि i th अरेंज (उसी के आकार का a)।

तो, बयान

return (&a)[n] - a; 

के बराबर है

return (&a)[n] - (&a)[0];  

और यह अंतर संकेत करने (&a)[n]वालों के बीच तत्वों की संख्या देगा (&a)[0], और जो nप्रत्येक n intतत्व के सरणियाँ हैं। इसलिए, कुल सरणी तत्व हैंn*n = n2 हैं


ध्यान दें:

C11: 6.5.6 एडिटिव ऑपरेटर (p9):

जब दो बिंदुओं को घटाया जाता है, तो दोनों एक ही एरे ऑब्जेक्ट के तत्वों को इंगित करेंगे, या एरे ऑब्जेक्ट के अंतिम तत्व को एक अतीत ; परिणाम दो सरणी तत्वों की सदस्यता का अंतर है। परिणाम का आकार कार्यान्वयन-परिभाषित है , और इसका प्रकार (एक हस्ताक्षरित पूर्णांक प्रकार) हेडर ptrdiff_tमें परिभाषित किया गया है <stddef.h>। यदि परिणाम उस प्रकार के ऑब्जेक्ट में प्रतिनिधित्व करने योग्य नहीं है, तो व्यवहार अपरिभाषित है।

चूँकि (&a)[n]न तो एक ही एरे ऑब्जेक्ट के तत्वों को इंगित करता है और न ही एरे ऑब्जेक्ट के अंतिम तत्व को अतीत में, अपरिभाषित व्यवहार(&a)[n] - a को आमंत्रित करेगा ।

यह भी ध्यान दें कि, फ़ंक्शन के रिटर्न प्रकार को बदलने के लिए बेहतर pहै ptrdiff_t


"दोनों समान सरणी ऑब्जेक्ट के तत्वों की ओर इशारा करेंगे" - जो मेरे लिए सवाल उठाता है कि क्या यह "हैक" यूबी नहीं है। सूचक अंकगणितीय अभिव्यक्ति एक गैर-मौजूद वस्तु के काल्पनिक अंत की बात कर रही है: क्या इसकी अनुमति है?
मार्टिन बा

संक्षेप में, एक n तत्वों की एक सरणी का पता है, इसलिए & [0] इस सरणी में पहले तत्व का पता है, जो कि एक ही है; इसके अलावा, और [k] को हमेशा n तत्वों की एक सरणी का पता माना जाएगा, भले ही k की परवाह किए बिना, और चूंकि [a। 1.n] एक वेक्टर है, साथ ही इसके तत्वों का "स्थान" लगातार है, जिसका अर्थ है पहला तत्व स्थिति x पर है, दूसरा स्थान x + पर है (वेक्टर के तत्वों की संख्या जो n है) और इसी तरह। क्या मैं सही हू? इसके अलावा, यह एक ढेर जगह है, तो क्या इसका मतलब है कि अगर मैं समान एन तत्वों के एक नए वेक्टर को आवंटित करता हूं, तो इसका पता ((ए) [1] के समान है?
इमानुएल

1
@Emanuel; सरणी &a[k]के kवें तत्व का एक पता है a। वह यह है (&a)[k]कि हमेशा kतत्वों की एक सरणी का पता माना जाएगा । तो, पहले तत्व की स्थिति में है a(या &a), दूसरे स्थान पर है a+ (सरणी के तत्वों की संख्या aहै जो n) * (एक सरणी तत्व के आकार) और इतने पर। और ध्यान दें कि, चर लंबाई सरणियों के लिए मेमोरी ढेर पर आवंटित की जाती है, ढेर पर नहीं।
haccks

@MartinBa; क्या यह भी अनुमति है? नहीं, इसकी अनुमति नहीं है। इसके यू.बी. संपादन देखें।
cks

1
@ प्रकृति के बीच अच्छा संयोग ot प्रश्न और आपका उपनाम
Dimitar Tsonev

35

aका (चर) सरणी है n int

&aएक (चर) सरणी में एक सूचक है n int

(&a)[1]का एक सूचक है intएक intपिछले सरणी तत्व अतीत। यह पॉइंटर n intबाद के तत्व हैं &a[0]

(&a)[2]का एक सूचक है intएक intदो सरणियों के अंतिम सरणी तत्व अतीत। यह पॉइंटर 2 * n intबाद के तत्व हैं &a[0]

(&a)[n]का एक सूचक है intएक intके अंतिम सरणी तत्व अतीत nसरणियों। यह पॉइंटर n * n intबाद के तत्व हैं &a[0]। बस घटाना &a[0]या aआपके पास है n

बेशक यह तकनीकी रूप से अपरिभाषित व्यवहार है भले ही यह आपकी मशीन पर काम करता हो क्योंकि (&a)[n]यह सरणी के अंदर इंगित नहीं करता है या पिछले सरणी तत्व के रूप में एक है (जैसा कि सूचक अंकगणित के सी नियमों द्वारा आवश्यक है)।


खैर, मुझे वह मिल गया, लेकिन सी में ऐसा क्यों होता है? इसके पीछे क्या तर्क है?
इमानुएल

@ इमानुएल के पास इस बात का कोई कड़ा जवाब नहीं है कि वास्तव में सूचक अंकगणित दूरी (आमतौर पर एक सरणी में) को मापने के लिए उपयोगी है, [n]वाक्यविन्यास एक सरणी की घोषणा करता है और बिंदुओं को विघटित करता है। इस परिणाम के साथ तीन अलग-अलग उपयोगी चीजें।
टॉमी

1
@ इमानुएल यदि आप पूछ रहे हैं कि कोई ऐसा क्यों करेगा, तो एब के कारण और प्रकृति के कारण नहीं है। और यह ध्यान देने योग्य है कि (&a)[n]यह प्रकार है int[n], और यहint* एरे के रूप में व्यक्त होने के कारण व्यक्त होता है, जो कि उनके पहले तत्व के पते के रूप में व्यक्त होता है, यदि विवरण में स्पष्ट नहीं था।
व्हाट्सक्रैग

नहीं, मेरा मतलब यह नहीं था कि कोई ऐसा क्यों करेगा, मेरा मतलब था कि सी मानक इस स्थिति में ऐसा क्यों करता है।
इमानुएल

1
@ इमानुएल पॉइंटर अंकगणित (और इस मामले में उस विषय का एक उपश्रेणी: सूचक भिन्नता )। इस साइट पर सवाल और जवाब पढ़ने के साथ-साथ यह देखने लायक है। इसके कई उपयोगी लाभ हैं और उचित रूप से उपयोग किए जाने पर मानकों में समवर्ती रूप से परिभाषित किया गया है। ताकि पूरी तरह से यह समझ में आप है को समझने के लिए प्रकार कोड में आप सूचीबद्ध काल्पनिक कर रहे हैं।
व्हाट्सक्रैग

12

यदि आपके पास दो बिंदु हैं जो एक ही सरणी के दो तत्वों को इंगित करते हैं तो इसका अंतर इन बिंदुओं के बीच तत्वों की संख्या पैदा करेगा। उदाहरण के लिए यह कोड स्निपेट 2 आउटपुट करेगा।

int a[10];

int *p1 = &a[1];
int *p2 = &a[3];

printf( "%d\n", p2 - p1 ); 

अब अभिव्यक्ति पर विचार करते हैं

(&a)[n] - a;

इसमें अभिव्यक्ति aके प्रकार हैंint * इसके पहले तत्व के और बिंदु हैं।

अभिव्यक्ति &aप्रकार int ( * )[n]और अंक दो आयामी सरणी की पहली पंक्ति के लिए है। इसके मूल्य से मेल खाता है, aहालांकि प्रकार भिन्न हैं।

( &a )[n]

इस imaged दो आयामी सरणी का n-th तत्व है और इसका प्रकार int[n]है यह imaged सरणी की n-th पंक्ति है। अभिव्यक्ति में(&a)[n] - a यह अपने पहले तत्व के पते में बदल जाता है और इसमें 'int * लिखा होता है।

तो बीच (&a)[n]और an तत्वों की n पंक्तियाँ हैं। तो अंतर बराबर होगा n * n


तो हर सरणी के पीछे आकार n * n का एक मैट्रिक्स है?
इमानुएल

@Emanuel इन दो बिंदुओं के बीच nxn तत्वों का एक मैट्रिक्स है। और बिंदुओं का अंतर n * n के बराबर मूल्य देता है यानी बिंदुओं के बीच कितने तत्व हैं।
21

लेकिन आकार का यह मैट्रिक्स n * n पीछे क्यों है? क्या इसका C में कोई उपयोग है? मेरा मतलब है, यह सी आकार के और अधिक सरणियों "सी" की तरह है, मुझे यह जाने बिना? यदि हां, तो क्या मैं उनका उपयोग कर सकता हूं? अन्यथा, इस मैट्रिक्स का गठन क्यों किया जाएगा (मेरा मतलब है, इसका वहां होने का एक उद्देश्य होना चाहिए)।
इमानुएल

2
@ इमानुएल - यह मैट्रिक्स केवल इस बात का स्पष्टीकरण है कि इस मामले में सूचक अंकगणितीय कैसे काम करता है। यह मैट्रिक्स आवंटित नहीं किया गया है और आप इसका उपयोग नहीं कर सकते हैं। जैसा कि यह पहले ही कुछ बार कहा गया था, 1) यह कोड स्निपेट एक हैक है जिसका कोई व्यावहारिक उपयोग नहीं है; 2) आपको यह जानने की जरूरत है कि इस हैक को समझने के लिए सूचक अंकगणित कैसे काम करता है।
void_ptr

@ इमानुएल यह सूचक अंकगणित की व्याख्या करता है। निष्कासन (ए) [एन] सूचक दो आयामी सरणी के सूचक सूचक के कारण n- तत्व का सूचक है।
को मॉस्को

4
Expression     | Value                | Explanation
a              | a                    | point to array of int elements
a[n]           | a + n*sizeof(int)    | refer to n-th element in array of int elements
-------------------------------------------------------------------------------------------------
&a             | a                    | point to array of (n int elements array)
(&a)[n]        | a + n*sizeof(int[n]) | refer to n-th element in array of (n int elements array)
-------------------------------------------------------------------------------------------------
sizeof(int[n]) | n * sizeof(int)      | int[n] is a type of n-int-element array

इस प्रकार,

  1. का प्रकार (&a)[n]हैint[n]पॉइंटर
  2. पॉइंटर का प्रकार aहैint

अब अभिव्यक्ति (&a)[n]-aएक सूचक स्थानापन्न करती है:

  (&a)[n]-a
= ((a + n*sizeof(int[n])) - a) / sizeof(int)
= (n * sizeof(int[n])) / sizeof(int)
= (n * n * sizeof(int)) / sizeof(int)
= n * n
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.