जब सी कोड में परिभाषा लिखी जाती है, तो सी भाषा में डेटा और कार्यों की * घोषणा * आवश्यक क्यों है?


15

निम्नलिखित "सी" कोड पर विचार करें:

#include<stdio.h>
main()
{   
  printf("func:%d",Func_i());   
}

Func_i()
{
  int i=3;
  return i;
}

Func_i()स्रोत कोड के अंत में परिभाषित किया गया है और इसके उपयोग से पहले कोई घोषणा प्रदान नहीं की गई है main()। जब कंपाइलर देखता Func_i()है main(), उसी समय वह बाहर आ जाता है main()और उसे पता चलता है Func_i()। कंपाइलर किसी तरह मूल्य को वापस पाता है Func_i()और उसे देता है printf()। मुझे यह भी पता है कि कंपाइलर रिटर्न प्रकार नहीं पा सकता है Func_i()। यह द्वारा चूक (अनुमान?) लेता वापसी प्रकार के Func_i()होने के लिए int। यदि कोड था float Func_i()तो संकलक त्रुटि देगा: के लिए संघर्षशील प्रकारFunc_i()

उपरोक्त चर्चा से हम देखते हैं कि:

  1. संकलक द्वारा दिए गए मान को पा सकता है Func_i()

    • यदि कंपाइलर Func_i()बाहर से आकर main()और सोर्स कोड को खोजकर लौटाए गए मान का पता लगा सकता है , तो वह Func_i () का प्रकार क्यों नहीं खोज सकता, जिसका स्पष्ट उल्लेख किया गया है।
  2. संकलक को पता होना चाहिए कि Func_i()प्रकार फ़्लोट का है - इसलिए यह परस्पर विरोधी प्रकारों की त्रुटि देता है।

  • यदि संकलक जानता है कि Func_iप्रकार फ़्लोट का है, तो यह अभी भी Func_i()टाइप इंट का क्यों मानता है, और परस्पर विरोधी प्रकारों की त्रुटि देता है? यह बलपूर्वक Func_i()फ्लोट के प्रकार का क्यों नहीं होता है ।

मुझे चर घोषणा के साथ एक ही संदेह है । निम्नलिखित "सी" कोड पर विचार करें:

#include<stdio.h>
main()
{
  /* [extern int Data_i;]--omitted the declaration */
  printf("func:%d and Var:%d",Func_i(),Data_i);
}

 Func_i()
{
  int i=3;
  return i;
}
int Data_i=4;

संकलक त्रुटि देता है: 'Data_i' अघोषित (इस फ़ंक्शन में पहला उपयोग)।

  • जब कंपाइलर देखता है Func_i(), तो यह Func_ () द्वारा लौटाए गए मान को खोजने के लिए स्रोत कोड पर जाता है। कंपाइलर चर Data_i के लिए समान क्यों नहीं कर सकता है?

संपादित करें:

मुझे कंपाइलर, असेंबलर, प्रोसेसर आदि की आंतरिक कार्यप्रणाली का विवरण नहीं पता है। मेरे प्रश्न का मूल विचार यह है कि यदि मैं उपयोग के बाद स्रोत कोड में फ़ंक्शन के रिटर्न-वैल्यू को बताऊं (लिखूं) उस फ़ंक्शन के बाद "सी" भाषा कंप्यूटर को बिना कोई त्रुटि दिए उस मूल्य को खोजने की अनुमति देती है। अब कंप्यूटर समान प्रकार क्यों नहीं खोज सकता है। Data_i के प्रकार को Func_i () के रिटर्न मान के रूप में क्यों नहीं पाया जा सकता है। यहां तक ​​कि अगर मैं extern data-type identifier;बयान का उपयोग करता हूं, तो मैं उस पहचानकर्ता (फ़ंक्शन / चर) द्वारा वापस किए जाने वाले मूल्य को नहीं बता रहा हूं। अगर कंप्यूटर उस मूल्य को पा सकता है तो वह प्रकार क्यों नहीं खोज सकता है। हमें आगे की घोषणा की आवश्यकता क्यों है?

धन्यवाद।


7
कंपाइलर Func_i द्वारा लौटाए गए मान को "खोज" नहीं करता है। यह निष्पादन समय पर किया जाता है।
जेम्स मैक्लियोड

26
मैं नीचे नहीं गया, लेकिन सवाल कुछ गंभीर गलतफहमियों पर आधारित है कि कंपाइलर कैसे काम करते हैं, और टिप्पणियों में आपकी प्रतिक्रियाएं आपको सुझाव देती हैं कि अभी भी कुछ वैचारिक बाधाओं को दूर करना है।
जेम्स मैकलेड 12

4
ध्यान दें कि पहला नमूना कोड पिछले पंद्रह वर्षों से मान्य, मानक अनुरूप कोड नहीं है; C99 ने फ़ंक्शन परिभाषाओं में वापसी प्रकार की अनुपस्थिति और Func_iअमान्य की निहित घोषणा की । कभी भी अपरिभाषित चर घोषित करने का नियम नहीं था, इसलिए दूसरा टुकड़ा हमेशा विकृत था। (हाँ, संकलक पहले नमूने को अभी भी स्वीकार करते हैं क्योंकि यह वैध था, अगर मैला, C89 / C90 के तहत।)
जोनाथन लेफ़लर

19
@ user31782: प्रश्न के लिए निचला रेखा: भाषा X को Y की आवश्यकता क्यों है? क्योंकि यह डिज़ाइनर्स की पसंद है। आप यह तर्क दे रहे हैं कि सबसे सफल भाषाओं में से एक के डिजाइनरों को दशकों पहले अलग-अलग विकल्प बनाने चाहिए थे, बजाय इसके कि वे उन संदर्भों को समझने की कोशिश करें, जो वे बनाए गए थे। आपके प्रश्न का उत्तर: हमें आगे की घोषणा की आवश्यकता क्यों है? दिया गया है: क्योंकि C एक-पास कंपाइलर का उपयोग करता है। आपके अधिकांश अनुवर्ती प्रश्नों का सबसे सरल उत्तर है क्योंकि तब यह एक-पास संकलक नहीं होगा।
मि। मिंडोर

4
@ user31782 आप वास्तव में, वास्तव में ड्रैगन की किताब को पढ़ना चाहते हैं ताकि समझने के लिए कि कंपाइलर और प्रोसेसर वास्तव में कैसे काम करते हैं - सभी आवश्यक ज्ञान को एक ही एसओ उत्तर (या यहां तक ​​कि 100 पर) में वितरित करना असंभव है। संकलक में रुचि रखने वाले किसी के लिए महान पुस्तक।
वु डे

जवाबों:


26

क्योंकि C एक एकल-पास है , जो सांख्यिकीय रूप से टाइप किया गया है , कमजोर रूप से टाइप किया गया है , संकलित भाषा है।

  1. एकल-पास का मतलब है कि कंपाइलर किसी फ़ंक्शन या वेरिएबल की परिभाषा को देखने के लिए आगे नहीं दिखता है। चूंकि कंपाइलर आगे नहीं दिखता है, फ़ंक्शन के उपयोग से पहले एक फ़ंक्शन की घोषणा को आना चाहिए, अन्यथा कंपाइलर को यह नहीं पता है कि इसका प्रकार हस्ताक्षर क्या है। हालाँकि, फ़ंक्शन की परिभाषा बाद में एक ही फ़ाइल में या पूरी तरह से एक अलग फ़ाइल में भी हो सकती है। बिंदु # 4 देखें।

    एकमात्र अपवाद ऐतिहासिक कलाकृति है जिसे अघोषित कार्यों और चर को "इंट" के प्रकार के रूप में माना जाता है। आधुनिक अभ्यास हमेशा स्पष्ट रूप से फ़ंक्शन और चर घोषित करके निहित टाइपिंग से बचने के लिए है।

  2. स्टैटिकली-टाइप्ड का अर्थ है कि सभी प्रकार की जानकारी संकलन समय पर गणना की जाती है। उस जानकारी का उपयोग मशीन कोड उत्पन्न करने के लिए किया जाता है जो कि रन टाइम पर निष्पादित होता है। रन-टाइम टाइपिंग के C में कोई कॉन्सेप्ट नहीं है। एक बार एक इंट, हमेशा एक इंट, एक बार एक फ्लोट, एक फ्लोट हमेशा। हालांकि, यह तथ्य अगले बिंदु से कुछ हद तक अस्पष्ट है।

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

  4. संकलित का अर्थ है कि मानव-पठनीय स्रोत कोड का विश्लेषण करने और उसे मशीन-पठनीय निर्देशों में बदलने की प्रक्रिया पूरी तरह से कार्यक्रम चलने से पहले की जाती है। जब कंपाइलर किसी फ़ंक्शन को संकलित कर रहा होता है, तो उसे इस बात का कोई ज्ञान नहीं होता है कि यह किसी दिए गए स्रोत फ़ाइल में आगे क्या होगा। हालांकि, एक बार संकलन (और असेंबली, लिंकिंग, आदि) पूरा हो गया है, समाप्त निष्पादन योग्य में प्रत्येक फ़ंक्शन में फ़ंक्शन के संख्यात्मक अंक होते हैं जो इसे चलाने पर कॉल करेगा। यही कारण है कि मुख्य () स्रोत फ़ाइल में नीचे एक फ़ंक्शन को कॉल कर सकता है। जब तक मुख्य () वास्तव में चलाया जाता है, तब तक इसमें Func_i () के पते पर एक सूचक होगा।

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

इन सभी अवधारणाओं को एक साथ रखकर, मुख्य कारण () फंक_आई () के प्रकार को निर्धारित करने के लिए आगे "नहीं" देख सकता है कि संकलन प्रक्रिया की शुरुआत में टाइप विश्लेषण होता है। उस बिंदु पर, मुख्य () की परिभाषा तक केवल स्रोत फ़ाइल का हिस्सा पढ़ा और विश्लेषण किया गया है, और फंक_आई () की परिभाषा अभी तक संकलक को नहीं पता है।

वह कारण जो मुख्य () "देख सकता है" जहां Func_i () को कॉल करना है वह यह है कि कॉलिंग रन टाइम पर होती है, संकलन के बाद सभी पहचानकर्ताओं के सभी नामों और प्रकारों को पहले ही हल कर दिया गया है, असेंबली पहले से ही सभी को परिवर्तित कर चुकी है मशीन कोड, और लिंकिंग के कार्यों को पहले से ही प्रत्येक फ़ंक्शन के सही पते को प्रत्येक स्थान पर सम्मिलित किया गया है जिसे यह कहा जाता है।

बेशक, मेरे पास अधिकांश विवरणों को छोड़ दिया गया है। वास्तविक प्रक्रिया बहुत अधिक जटिल है। मुझे आशा है कि मैंने आपके प्रश्नों का उत्तर देने के लिए पर्याप्त उच्च-स्तरीय अवलोकन प्रदान किया है।

इसके अतिरिक्त, कृपया याद रखें, जो मैंने ऊपर लिखा है, विशेष रूप से सी पर लागू होता है।

अन्य भाषाओं में, संकलक स्रोत कोड के माध्यम से कई पास बना सकता है, और इसलिए संकलक इसे पहले से घोषित किए बिना Func_i () की परिभाषा चुन सकता है।

अन्य भाषाओं में, फ़ंक्शंस और / या चर गतिशील रूप से टाइप किए जा सकते हैं, इसलिए एक एकल चर पकड़ सकता है, या एक फ़ंक्शन को अलग-अलग समय पर एक पूर्णांक, एक फ्लोट, एक स्ट्रिंग, एक सरणी, या एक ऑब्जेक्ट को पारित या वापस किया जा सकता है।

अन्य भाषाओं में, टाइपिंग अधिक मजबूत हो सकती है, जिससे फ्लोटिंग-पॉइंट से पूर्णांक तक रूपांतरण को स्पष्ट रूप से निर्दिष्ट करने की आवश्यकता होती है। अन्य भाषाओं में, टाइपिंग कमजोर हो सकती है, स्ट्रिंग "3.0" से फ्लोट 3.0 में पूर्णांक 3 में रूपांतरण को स्वचालित रूप से ले जाने की अनुमति देता है।

और अन्य भाषाओं में, कोड को एक बार में एक पंक्ति में व्याख्या किया जा सकता है, या बाइट-कोड पर संकलित किया जा सकता है और फिर व्याख्या की जा सकती है, या बस-में-समय संकलित किया जा सकता है, या अन्य निष्पादन योजनाओं की एक विस्तृत विविधता के माध्यम से डाला जा सकता है।


1
आप सभी को एक-एक उत्तर के लिए धन्यवाद। आपका और निक्की का जवाब वही है जो मैं जानना चाहता था। उदाहरण Func_()+1: यहाँ संकलन समय पर कंपाइलर कोFunc_i() उपयुक्त मशीन कोड उत्पन्न करने के लिए इसके प्रकार को जानना होगा । शायद या तो असेंबली के लिए रन टाइम पर टाइप करके हैंडल करना संभव नहीं हैFunc_()+1 , या यह संभव है लेकिन ऐसा करने से रन-टाइम पर प्रोग्राम धीमा हो जाएगा। मुझे लगता है, अभी के लिए यह मेरे लिए काफी है।
user106313

1
सी के अंतर्निहित घोषित कार्यों का महत्वपूर्ण विवरण: उन्हें प्रकार का माना जाता है int func(...)... अर्थात वे एक परिवर्तनशील तर्क सूची लेते हैं। इसका मतलब है कि यदि आप किसी फ़ंक्शन को परिभाषित int putc(char)करते हैं, लेकिन इसे घोषित करना भूल जाते हैं, तो इसे बदले में बुलाया जाएगा int putc(int)(क्योंकि एक चर तर्क सूची के माध्यम से पारित चार को बढ़ावा दिया जाता है int)। इसलिए जब ओपी का उदाहरण काम करने लगा, क्योंकि उसके हस्ताक्षर अंतर्निहित घोषणा से मेल खाते थे, यह समझ में आता है कि यह व्यवहार क्यों हतोत्साहित किया गया था (और उपयुक्त चेतावनियों को जोड़ा गया था)।
उलिवातू

37

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

int Func_i();

संकलक की मदद करने के लिए शीर्ष या शीर्ष लेख फ़ाइल में।

आपके उदाहरणों में, आप सी भाषा की दो संदिग्ध विशेषताओं का उपयोग करते हैं जिन्हें टाला जाना चाहिए:

  1. यदि किसी फ़ंक्शन को ठीक से घोषित करने से पहले उपयोग किया जाता है, तो इसका उपयोग "निहित घोषणा" के रूप में किया जाता है। कंपाइलर फंक्शन सिग्नेचर का पता लगाने के लिए तत्काल संदर्भ का उपयोग करता है। कंपाइलर बाकी कोड के माध्यम से यह पता लगाने के लिए स्कैन नहीं करेगा कि वास्तविक घोषणा क्या है।

  2. यदि किसी प्रकार के बिना कुछ घोषित किया जाता है, तो प्रकार को लिया जाता है int। यह उदाहरण के लिए स्थिर चर या फ़ंक्शन रिटर्न प्रकारों के लिए मामला है।

इसलिए printf("func:%d",Func_i()), हमारे पास एक निहित घोषणा है int Func_i()। जब कंपाइलर फ़ंक्शन परिभाषा तक पहुंचता है Func_i() { ... }, तो यह प्रकार के साथ संगत है। लेकिन अगर आपने float Func_i() { ... }इस बिंदु पर लिखा है , तो आपके पास अंतर्निहित घोषणा int Func_i()और स्पष्ट रूप से घोषित की गई है float Func_i()। चूंकि दो घोषणाएं मेल नहीं खाती हैं, इसलिए कंपाइलर आपको एक त्रुटि देता है।

कुछ भ्रांतियों को दूर कर रहे हैं

  • संकलक द्वारा दिए गए मान को नहीं पाता है Func_i। एक स्पष्ट प्रकार की अनुपस्थिति का मतलब है कि वापसी प्रकार intडिफ़ॉल्ट रूप से है। अगर आप ऐसा करते हैं:

    Func_i() {
        float f = 42.3;
        return f;
    }

    फिर प्रकार होगा int Func_i(), और वापसी मूल्य चुपचाप काट दिया जाएगा!

  • संकलक को अंततः वास्तविक प्रकार का पता चल जाता है Func_i, लेकिन यह निहित घोषणा के दौरान वास्तविक प्रकार को नहीं जानता है। केवल जब यह बाद में वास्तविक घोषणा तक पहुंचता है, तो यह पता लगा सकता है कि क्या अंतर्निहित घोषित प्रकार सही था। लेकिन उस समय, फ़ंक्शन कॉल के लिए असेंबली पहले से ही लिखी गई हो सकती है और सी संकलन मॉडल में नहीं बदला जा सकता है।


3
@ user31782: कोड का क्रम संकलित समय पर मायने रखता है, लेकिन रन समय पर नहीं। जब प्रोग्राम चलता है तो कंपाइलर तस्वीर से बाहर होता है। रन टाइम तक, फ़ंक्शन को इकट्ठा और लिंक किया गया होगा, इसका पता हल हो गया होगा और कॉल के एड्रेस प्लेसहोल्डर में अटक जाएगा। (यह उससे थोड़ा अधिक जटिल है, लेकिन यह मूल विचार है।) प्रोसेसर आगे या पीछे शाखा कर सकता है।
१३:

20
@ user31782: संकलक मूल्य नहीं छापता है। आपका संकलक प्रोग्राम नहीं चलाता है !!
मोनिका

1
@LightnessRacesinOrbit मुझे पता है कि। मैंने गलती से ऊपर अपनी टिप्पणी में संकलक लिखा था क्योंकि मैं नाम प्रोसेसर को भूल गया था ।
user106313

3
@Carcigenicate C, B भाषा से अत्यधिक प्रभावित था, जिसमें केवल एक ही प्रकार था: एक शब्द-चौड़ाई अभिन्न सांख्यिक प्रकार जो बिंदुओं के लिए भी इस्तेमाल किया जा सकता था। C ने मूल रूप से इस व्यवहार की नकल की है, लेकिन अब यह C99 मानक के बाद से पूरी तरह से अवैध है। Unitएक टाइप-थ्योरी दृष्टिकोण से एक अच्छा डिफ़ॉल्ट प्रकार बनाता है, लेकिन बी और फिर सी के लिए डिज़ाइन किए गए मेटल सिस्टम प्रोग्रामिंग के करीब व्यावहारिकता में विफल रहता है।
आमोन

2
@ user31782: प्रोसेसर के लिए सही असेंबली बनाने के लिए कंपाइलर को चर के प्रकार का पता होना चाहिए। जब संकलक को निहित पता चलता है Func_i(), तो यह तुरंत प्रोसेसर के लिए कोड को किसी अन्य स्थान पर कूदने के लिए उत्पन्न करता है और सहेजता है, फिर कुछ पूर्णांक प्राप्त करने के लिए, और फिर जारी रखें। जब कंपाइलर को बाद में Func_iपरिभाषा मिलती है , तो यह सुनिश्चित करता है कि हस्ताक्षर मेल खाते हैं, और यदि वे करते हैं, तो यह Func_i()उस पते के लिए विधानसभा रखता है , और इसे कुछ पूर्णांक वापस करने के लिए कहता है। जब आप प्रोग्राम चलाते हैं, तो प्रोसेसर उन निर्देशों को मान के साथ अनुसरण करता है 3
मूविंग डक

10

सबसे पहले, आप प्रोग्राम C90 मानक के लिए मान्य हैं, लेकिन निम्नलिखित के लिए नहीं। अंतर्निहित int (इसकी वापसी प्रकार दिए बिना किसी फ़ंक्शन को घोषित करने की अनुमति देना), और फ़ंक्शन की अंतर्निहित घोषणा (इसे घोषित किए बिना फ़ंक्शन का उपयोग करने की अनुमति देना) कोई अधिक वैध नहीं है।

दूसरा, जैसा आप सोचते हैं वैसा काम नहीं करता है।

  1. परिणाम प्रकार C90 में वैकल्पिक हैं, एक intपरिणाम देने का मतलब नहीं है । यह भी चर घोषणा के लिए सही है (लेकिन आपको एक भंडारण वर्ग देना होगा, staticया extern)।

  2. कंपाइलर जब Func_iपिछली घोषणा के बिना कॉल करता है, तो यह मान लेता है कि कोई घोषणा है

    extern int Func_i();

    यह देखने के लिए कोड में आगे नहीं दिखता है कि कैसे प्रभावी रूप Func_iसे घोषित किया गया है। यदि Func_iघोषित या परिभाषित नहीं किया गया था, तो संकलन करते समय कंपाइलर अपना व्यवहार नहीं बदलेगा main। निहित घोषणा केवल कार्य के लिए है, चर के लिए कोई भी नहीं है।

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


यदि कंपाइलर मुख्य () से बाहर आकर Func_i () द्वारा लौटाए गए मान का पता लगा सकता है और स्रोत कोड को खोज सकता है, तो वह Func_i () के प्रकार को क्यों नहीं खोज सकता है, जिसका स्पष्ट रूप से उल्लेख किया गया है।
user106313

1
@ user31782 अगर Func_i की कोई पूर्व घोषणा नहीं थी, तो यह देखते हुए कि Func_i का उपयोग कॉल एक्सप्रेशन में किया जाता है, ऐसा व्यवहार करता है जैसे कि एक था extern int Func_i()। यह कहीं नहीं दिखता है।
एपीग्रामग्राम

1
@ user31782, संकलक कहीं भी नहीं कूदता है। यह उस फ़ंक्शन को कॉल करने के लिए कोड का उत्सर्जन करेगा; लौटाया गया मान रन-टाइम पर निर्धारित किया जाएगा। वैसे, ऐसे साधारण फ़ंक्शन के मामले में जो एक ही संकलन इकाई में मौजूद है, अनुकूलन चरण फ़ंक्शन को इनलाइन कर सकता है, लेकिन यह कुछ ऐसा नहीं है जिसके बारे में आपको भाषा के नियमों पर विचार करते समय सोचना चाहिए, यह एक अनुकूलन है।
एपीग्रामग्राम

10
@ user31782, आपके पास गंभीर गलतफहमी है कि कार्यक्रम कैसे काम करते हैं। इतना गंभीर कि मुझे नहीं लगता कि p.se उन्हें ठीक करने के लिए एक अच्छी जगह है (शायद चैट, लेकिन मैं इसे करने की कोशिश नहीं करूंगा)।
अप्रैलग्राम

1
@ user31782: एक छोटा सा स्निपेट लिखना और इसे -S(यदि आप उपयोग कर रहे हैं gcc) के साथ संकलित करना आपको कंपाइलर द्वारा उत्पन्न असेंबली कोड को देखने की अनुमति देगा। फिर आपको इस बात का अंदाजा हो सकता है कि रन-टाइम पर रिटर्न-वैल्यू को कैसे हैंडल किया जाता है (आमतौर पर प्रोसेसर रजिस्टर या प्रोग्राम स्टैक पर कुछ जगह का उपयोग करके)।
जियोर्जियो

7

आपने एक टिप्पणी में लिखा है:

निष्पादन लाइन-बाय-लाइन किया जाता है। Func_i () द्वारा लौटाए गए मान को खोजने का एकमात्र तरीका मुख्य से बाहर कूदना है

यह एक गलत धारणा है: निष्पादन डॉन-बाय-लाइन नहीं है। संकलन लाइन द्वारा लाइन में किया जाता है, और नाम रिज़ॉल्यूशन संकलन के दौरान किया जाता है, और यह केवल नाम हल करता है, मान वापस नहीं करता है।

एक सहायक वैचारिक मॉडल यह है: जब कंपाइलर लाइन पढ़ता है:

  printf("func:%d",Func_i());

यह कोड के बराबर उत्सर्जन करता है:

  1. call "function #2" and put the return value on the stack
  2. put the constant string "func:%d" on the stack
  3. call "function #1"

कंपाइलर कुछ आंतरिक तालिका में एक नोट भी बनाता है जिसे function #2अभी तक घोषित नहीं किया गया फ़ंक्शन नाम दिया गया है Func_i, जो अनिर्दिष्ट संख्या में तर्क लेता है और एक इंट (डिफ़ॉल्ट) देता है।

बाद में, जब यह इसे पार्स करता है:

 int Func_i() { ...

कंपाइलर Func_iऊपर उल्लिखित तालिका में दिखता है और जांचता है कि क्या पैरामीटर और रिटर्न प्रकार मेल खाते हैं। यदि वे नहीं करते हैं, तो यह एक त्रुटि संदेश के साथ बंद हो जाता है। यदि वे करते हैं, तो यह वर्तमान पता आंतरिक फ़ंक्शन तालिका में जोड़ता है और अगली पंक्ति पर जाता है।

इसलिए, कंपाइलर ने Func_iपहले संदर्भ को पार्स करने पर "लुक" नहीं दिया। यह बस किसी तालिका में एक नोट बनाता है, अगली पंक्ति को पार्स करता है। और फ़ाइल के अंत में, इसमें एक ऑब्जेक्ट फ़ाइल है, और कूद पते की एक सूची है।

बाद में, लिंकर यह सब ले जाता है, और वास्तविक कूद पते के साथ सभी फ़ंक्शन को "फ़ंक्शन # 2" में बदल देता है, इसलिए यह कुछ इस तरह का उत्सर्जन करता है:

  call 0x0001215 and put the result on the stack
  put constant ... on the stack
  call ...
...
[at offset 0x0001215 in the file, compiled result of Func_i]:
  put 3 on the stack
  return top of the stack

बहुत बाद में, जब निष्पादन योग्य फ़ाइल चलती है, तो कूद पता पहले से ही हल हो जाता है, और कंप्यूटर केवल 0x1215 पते पर कूद सकता है। कोई नाम लुकअप आवश्यक नहीं है।

अस्वीकरण : जैसा कि मैंने कहा, यह एक वैचारिक मॉडल है, और वास्तविक दुनिया अधिक जटिल है। कंपाइलर और लिंकर्स आज हर तरह के क्रेजी ऑप्टिमाइजेशन करते हैं। वे यहां तक कि "नीचे कूदने के लिए" देख सकते हैं Func_i, हालांकि मुझे संदेह है। लेकिन सी भाषाओं को इस तरह से परिभाषित किया गया है कि आप उस तरह से एक सुपर-सरल संकलक लिख सकते हैं । तो ज्यादातर समय, यह एक बहुत ही उपयोगी मॉडल है।


आपके उत्तर के लिए धन्यवाद। संकलक कोड का उत्सर्जन नहीं कर सकता है:1. call "function #2", put the return-type onto the stack and put the return value on the stack?
user106313

1
(कंट।) इसके अलावा: क्या होगा यदि आपने लिखा है printf(..., Func_i()+1);- संकलक को इसके प्रकार को जानना है Func_i, इसलिए यह तय कर सकता है कि इसे add integerया एक add floatनिर्देश का उत्सर्जन करना चाहिए । आपको कुछ विशेष मामले मिल सकते हैं जहां कंपाइलर बिना किसी प्रकार की जानकारी के जा सकता है, लेकिन कंपाइलर को सभी मामलों के लिए काम करना होगा ।
nikie

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

1
@ user31782: निर्भर करता है, IIRC। floatमूल्य एफपीयू रजिस्टर में रह सकते हैं - फिर कोई निर्देश नहीं होगा। कंपाइलर सिर्फ ट्रैक रखता है कि कौन सा मूल्य संकलित करने के दौरान किस रजिस्टर में संग्रहीत किया गया है, और "एफपी रजिस्टर एक्स में निरंतर 1 जोड़ें" जैसे सामान का उत्सर्जन करता है। या यह स्टैक पर रह सकता है, अगर कोई मुफ्त रजिस्टर नहीं है। फिर "स्टैक पॉइंटर को 4" से बढ़ाना होगा, और वैल्यू को "स्टैक पॉइंटर" - 4 "की तरह" संदर्भित "किया जाएगा।" लेकिन ये सभी चीजें केवल तभी काम करती हैं जब स्टैक पर सभी चर (पहले और बाद) के आकार को संकलन-समय पर जाना जाता है।
नीकी

1
सभी चर्चा से मैं इस समझ तक पहुँच गया हूँ: संकलक के लिए Func_i()या / और Data_i, सहित किसी भी बयान के लिए एक प्रशंसनीय विधानसभा कोड बनाने के लिए , यह उनके प्रकार निर्धारित करना है; विधानसभा भाषा में डेटा प्रकार पर कॉल करना संभव नहीं है। मुझे आश्वस्त होने के लिए चीजों का विस्तार से अध्ययन करने की आवश्यकता है।
user106313

5

सी और कई अन्य भाषाएं जिन्हें घोषणाओं की आवश्यकता होती है, एक ऐसे युग में डिजाइन किए गए थे जब प्रोसेसर समय और मेमोरी महंगी थी। सी और यूनिक्स के विकास में कुछ समय के लिए हाथ चला गया, और बाद में वर्चुअल मेमोरी नहीं थी जब तक 1979 में 3BSD दिखाई नहीं दिया। काम करने के लिए अतिरिक्त कमरे के बिना, संकलक एकल-पास मामलों के लिए जाते थे क्योंकि वे नहीं थे एक ही बार में पूरी फ़ाइल के कुछ प्रतिनिधित्व को रखने की क्षमता की आवश्यकता होती है।

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

प्रारंभिक सी (एटी एंड टी, के एंड आर, C89) में, foo()घोषणा से पहले एक फ़ंक्शन का उपयोग करने के परिणामस्वरूप एक वास्तविक या अनुमानित घोषणा होती है int foo()। आपका उदाहरण काम करता है जब Func_i()घोषित किया जाता है intक्योंकि यह मेल खाता है जो आपकी ओर से घोषित किया गया है। इसे किसी अन्य प्रकार में बदलने से विरोधाभास होगा क्योंकि यह अब मेल नहीं खाता है कि संकलक ने स्पष्ट घोषणा के अभाव में क्या चुना। यह व्यवहार C99 में हटा दिया गया था, जहां एक अघोषित फ़ंक्शन का उपयोग एक त्रुटि बन गया।

तो वापसी के प्रकार के बारे में क्या?

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

प्रारंभिक सी की एक अन्य विशेषता यह थी कि प्रोटोटाइप जैसा कि हम जानते हैं कि अब उनका अस्तित्व नहीं था। आप एक फ़ंक्शन का रिटर्न प्रकार (जैसे int foo()) घोषित कर सकते हैं , लेकिन इसके तर्क नहीं (यानी, int foo(int bar)एक विकल्प नहीं था)। यह अस्तित्व में था, क्योंकि जैसा कि ऊपर उल्लिखित है, कार्यक्रम हमेशा एक कॉलिंग कन्वेंशन से जुड़ा होता है जिसे तर्कों द्वारा निर्धारित किया जा सकता है। यदि आपने किसी फ़ंक्शन को गलत प्रकार के तर्कों के साथ बुलाया है, तो यह एक कचरा है, कचरा बाहर की स्थिति है।

क्योंकि ऑब्जेक्ट कोड में रिटर्न की धारणा होती है, लेकिन रिटर्न टाइप की नहीं, कंपाइलर को रिटर्न वैल्यू से निपटने के लिए रिटर्न टाइप जानना होता है। जब आप मशीन निर्देश चला रहे होते हैं, तो यह सब सिर्फ बिट्स होता है और प्रोसेसर को इस बात की परवाह नहीं है कि आप doubleवास्तव में तुलना करने की कोशिश कर रहे हैं या नहीं int। यह वही करता है जो आप पूछते हैं, और यदि आप इसे तोड़ते हैं, तो आप दोनों टुकड़ों के मालिक हैं।

कोड के इन बिट्स पर विचार करें:

double foo();         double foo();
double x;             int x;
x = foo();            x = foo();

बाईं ओर का कोड एक कॉल के लिए नीचे संकलित करता है, foo()जहां कॉल / रिटर्न कन्वेंशन के माध्यम से प्रदान किए गए परिणाम को कॉपी करके xसंग्रहीत किया जाता है। यह आसान मामला है।

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

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


आपके उत्तर के लिए धन्यवाद (+1)। मुझे कंपाइलर, असेंबलर, प्रोसेसर आदि के आंतरिक कामकाज का विवरण नहीं पता है। मेरे प्रश्न का मूल विचार यह है कि यदि मैं उपयोग के बाद स्रोत कोड में फ़ंक्शन के रिटर्न-वैल्यू को बताता हूं (लिखता हूं) उस फ़ंक्शन के बाद भाषा किसी भी त्रुटि के बिना कंप्यूटर को उस मूल्य को खोजने की अनुमति देती है । अब कंप्यूटर समान प्रकार क्यों नहीं खोज सकता है । Data_i का प्रकार क्यों नहीं पाया जा सकता है क्योंकि Func_i()रिटर्न मान पाया गया था।
user106313

मैं अब भी संतुष्ट नहीं हूं। double foo(); int x; x = foo();बस त्रुटि देता है। मुझे पता है कि हम ऐसा नहीं कर सकते। मेरा सवाल यह है कि फ़ंक्शन कॉल में प्रोसेसर केवल रिटर्न-वैल्यू पाता है; यह रिटर्न-टाइप भी क्यों नहीं खोज सकता है?
user106313

1
@ user31782: ऐसा नहीं होना चाहिए। के लिए एक प्रोटोटाइप है foo(), इसलिए कंपाइलर जानता है कि इसके साथ क्या करना है।
ब्लरफ्ल

2
@ user31782: प्रोसेसर के पास रिटर्न प्रकार की कोई धारणा नहीं है।
Blrfl

1
@ user31782 संकलन समय प्रश्न के लिए: ऐसी भाषा लिखना संभव है जिसमें इस प्रकार का विश्लेषण संकलन समय पर किया जा सके। C ऐसी भाषा नहीं है। C संकलक ऐसा नहीं कर सकता क्योंकि इसे करने के लिए डिज़ाइन नहीं किया गया है। क्या इसे अलग तरह से डिजाइन किया जा सकता था? ज़रूर, लेकिन यह ऐसा करने के लिए बहुत अधिक प्रसंस्करण शक्ति और मेमोरी लेगा। निचला रेखा यह नहीं है। यह इस तरीके से डिज़ाइन किया गया था कि दिन के कंप्यूटर सबसे अच्छी तरह से निपटने में सक्षम थे।
मि। मिंडोर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.