C में प्रोग्रामिंग

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

टोकन, पहचानकर्ता व नियंत्रण-प्रवाह

  • C के टोकन कीवर्ड, पहचानकर्ता, अचर, स्ट्रिंग शाब्दिक, संकारक व विरामचिह्न हैं; पहचानकर्ता का कार्यक्षेत्र (ब्लॉक, फलन, फ़ाइल/वैश्विक) व भंडारण-वर्ग (auto, register, static, extern) स्वतंत्र गुण हैं — फलन के भीतर static किसी स्थानीय चर की आयु को पूरे प्रोग्राम तक विस्तारित करता है जबकि उसका कार्यक्षेत्र स्थानीय ही छोड़ता है, जो इस उप-प्रकरण का मानक जाल है।
  • अनुक्रम नियंत्रण: if-else, switch (जो break न होने पर स्थितियों में गिरता चला जाता है — एक मानक जाल), while, do-while (मुख्य भाग कम-से-कम एक बार चलता है), for। उपप्रोग्राम नियंत्रण: सामान्य प्राचलों हेतु कॉल-बाय-वैल्यू शब्दार्थ वाले फलन (आमंत्रित को एक प्रति मिलती है; परिवर्तन वापस प्रसारित नहीं होते) — आमंत्रक के चर को प्रभावित करने का एकमात्र तरीका उसका पता स्पष्ट रूप से पास करना व आमंत्रित के भीतर संकेतक को डी-रेफ़रेंस करना है।

सरणी, स्ट्रक्ट, यूनियन व स्ट्रिंग

C सरणी का नाम, लगभग हर व्यंजक-संदर्भ में, अपने प्रथम अवयव के संकेतक में क्षय होता है — arr[i] को *(arr + i) के रूप में परिभाषित किया गया है, इसीलिए सरणी-सूचकांकन व संकेतक-अंकगणित समान परिणाम देते हैं, और sizeof(arr) (पूरी सरणी का बाइट-आकार) एक बार arr किसी फलन-प्राचल में क्षय हो जाने पर sizeof(ptr) से तीव्रता से भिन्न हो जाता है (वहाँ यह मात्र संकेतक का अपना आकार है)। स्ट्रक्ट नामित, स्वतंत्र रूप से संचित क्षेत्रों को समूहित करता है — उसका कुल आकार सामान्यतः उसके सदस्यों के आकारों के योग से कम-से-कम उतना है, और प्रायः अधिक, संकलक द्वारा प्रत्येक सदस्य की अपनी संरेखण-आवश्यकता हेतु डाली गई पैडिंग के कारण। C में स्ट्रिंग बिल्कुल भिन्न प्रकार नहीं है: यह '\0' (NUL) बाइट से समाप्त char सरणी है, अतः हर स्ट्रिंग-फलन (strlen, strcpy, strcat) वह समापक मिलने तक खोजता है, और उसके लिए स्थान छोड़ना भूल जाना मानक बफ़र-अतिप्रवाह का स्रोत है।

⚠️ सरणी-प्राचल का sizeof मानक जाल है
जिस फलन में यह घोषित है उसके भीतर, int arr[10] का sizeof(arr) == 40 है (4-बाइट int पर)। एक बार फलन-प्राचल के रूप में पास होने पर, void f(int arr[10]) को संकलक चुपचाप void f(int *arr) में पुनर्लिखित कर देता है, और f के भीतर sizeof(arr) संकेतक का अपना आकार देता है (64-बिट तंत्र पर सामान्यतः 8) — सरणी की मूल लंबाई पृथक रूप से पास किए बिना बस लुप्त हो जाती है।

संकेतक व फलन

  • संकेतक चर एक पता रखता है; *p उसे डी-रेफ़रेंस करता है (इंगित मान तक पहुँचता है), &x x का पता लेता है। संकेतक-अंकगणित इंगित प्रकार के आकार से मापित होता है: int* पर p + 1, 1 बाइट नहीं बल्कि sizeof(int) बाइट आगे बढ़ता है — यही वह है जो संकेतकों से सरणी-सूचकांकन को अवयव-आकार से स्वतंत्र रूप से सही बनाता है।
  • फलन संकेतक किसी फलन के कोड का पता रखता है और उससे होकर बुलाया, सौंपा, या कॉलबैक के रूप में पास किया जा सकता है — qsort के तुलनक-आर्गुमेंट के पीछे की क्रियाविधि, व अधिक सामान्यतः, किसी भी C-शैली प्लग-इन प्रेषण-सारणी के पीछे की।
  • गतिक स्मृति (malloc/calloc/realloc/free) हीप पर आवंटित होती है और उसे आवंटित करने वाले फलन से अधिक जीवित रहती है — मानक बग हैं स्मृति-रिसाव (free भूलना), लटकता संकेतक (मुक्त होने के बाद स्मृति का प्रयोग) व द्वि-मुक्ति (उसी खंड को दो बार मुक्त करना)।

फ़ाइल-प्रबंधन, कमांड-लाइन आर्गुमेंट व प्रीप्रोसेसर

  • फ़ाइल-प्रबंधन: fopen एक FILE* लौटाता है (विफलता पर NULL — प्रयोग से पहले सदा जाँचा जाता है); fread/fwrite द्विआधारी I/O करते हैं, fscanf/fprintf प्रारूपित पाठ-I/O; fclose बफ़र किए निर्गम को फ्लश करता है व हैंडल मुक्त करता है, और इसे भूल जाना बफ़र में पड़ा वह आँकड़ा खो सकता है जो वास्तव में डिस्क पर कभी लिखा ही नहीं गया।
  • कमांड-लाइन आर्गुमेंट main के प्राचलों int argc, char *argv[] में आते हैं — argc में प्रोग्राम का अपना नाम भी गिना जाता है (अतः argv[0] प्रोग्राम का अपना नाम है, और पहला वास्तविक उपयोगकर्ता-आर्गुमेंट argv[1] है)।
  • प्रीप्रोसेसर निर्देश (#define, #include, #ifdef/#ifndef, प्राचल सहित मैक्रो) वास्तविक संकलन से पहले चलते हैं, शुद्ध पाठ्य-प्रतिस्थापन करते हुए — मैक्रो प्रकार-जाँचित नहीं होता और फलन की भाँति कार्यक्षेत्रित नहीं होता, इसीलिए कोष्ठक-युक्त मैक्रो #define SQ(x) ((x)*(x)) को उन कोष्ठकों की आवश्यकता है ताकि SQ(a+b) जैसे बिना-कोष्ठक चुपचाप गलत विस्तार से बचा जा सके।

मुख्य बिंदु

  • भंडारण-वर्ग (auto/static/extern/register) व कार्यक्षेत्र स्वतंत्र हैं — फलन के भीतर static कार्यक्षेत्र स्थानीय रखते हुए आयु पूरे प्रोग्राम तक विस्तारित करता है।
  • C कड़ाई से कॉल-बाय-वैल्यू है; आमंत्रक के चर को प्रभावित करने हेतु उसका पता पास करना व आमंत्रित के भीतर डी-रेफ़रेंस करना आवश्यक है।
  • सरणी-प्राचल चुपचाप संकेतक बन जाता है — sizeof सरणी का वास्तविक आकार केवल उस फलन के भीतर देता है जहाँ वह घोषित है, प्राचल के रूप में पास होने के बाद कभी नहीं।
  • C स्ट्रिंग NUL से समाप्त char सरणी है, भिन्न प्रकार नहीं; समापक हेतु स्थान भूलना मानक बफ़र-अतिप्रवाह का स्रोत है।
  • प्रीप्रोसेसर संकलन से पहले शुद्ध पाठ्य-प्रतिस्थापन करता है — बिना-कोष्ठक मैक्रो-प्राचल चुपचाप गलत विस्तार दे सकता है।

अभ्यास प्रश्न (8)

उत्तर खोलने से पहले प्रत्येक प्रश्न हल करें। हर व्याख्या सही विकल्प के साथ लुभावना गलत विकल्प भी बताती है, क्योंकि अंक वहीं जाते हैं।

  1. C फलन के भीतर static घोषित चर का यह गुण है:

    1. स्थानीय कार्यक्षेत्र पर पूरे प्रोग्राम तक विस्तारित आयु
    2. वैश्विक कार्यक्षेत्र व स्थानीय आयु
    3. वैश्विक कार्यक्षेत्र व वैश्विक आयु दोनों
    4. जिस कथन में यह घोषित है उससे आगे न कार्यक्षेत्र न आयु
    उत्तर देखें

    उत्तर: A — स्थानीय कार्यक्षेत्र पर पूरे प्रोग्राम तक विस्तारित आयु

    static आयु बदलता है (चर कॉलों के आर-पार अपना मान बनाए रखता है, प्रोग्राम की पूरी दौड़ हेतु एक बार आवंटित) कार्यक्षेत्र बदले बिना, जो उसी फलन तक सीमित रहता है जिसमें यह घोषित है।
  2. C में, किसी फलन से आमंत्रक के चर का मान बदलवाने हेतु, फलन को यह करना ही चाहिए:

    1. प्राचल के रूप में चर का पता (संकेतक) प्राप्त कर उसे डी-रेफ़रेंस करना
    2. बस उसी नाम से प्राचल घोषित करना
    3. कुछ विशेष नहीं — C स्वतः सभी चर संदर्भ द्वारा पास करता है
    4. केवल एक वैश्विक चर का प्रयोग करना
    उत्तर देखें

    उत्तर: A — प्राचल के रूप में चर का पता (संकेतक) प्राप्त कर उसे डी-रेफ़रेंस करना

    C कड़ाई से कॉल-बाय-वैल्यू है; सामान्य प्राचल एक प्रति है। चर का पता स्पष्ट रूप से पास करना व आमंत्रित के भीतर डी-रेफ़रेंस्ड संकेतक से होकर लिखना ही आमंत्रक के अपने भंडारण को प्रभावित करने का एकमात्र तरीका है।
  3. उसी फलन के भीतर घोषित व प्रयुक्त int arr[10]; हेतु, sizeof(arr) (4-बाइट int सहित) बराबर है:

    1. 40
    2. 4
    3. 8
    4. 10
    उत्तर देखें

    उत्तर: A — 40

    अपने ही घोषक फलन के भीतर, सरणी अभी संकेतक में क्षय नहीं हुई है, अतः sizeof सरणी का कुल बाइट-आकार देता है: 10 अवयव × 4 बाइट = 40। यह एक बार arr फलन-प्राचल के रूप में पास होने पर ही संकेतक के अपने आकार (सामान्यतः 8) में बदलता है।
  4. C में यूनियन, समान क्षेत्रों वाले स्ट्रक्ट की तुलना में:

    1. सभी क्षेत्रों को उसी स्मृति पर अध्यारोपित करता है, अपने सबसे बड़े सदस्य के आकार का
    2. स्ट्रक्ट की भाँति हर क्षेत्र हेतु एक साथ पृथक स्मृति आवंटित करता है
    3. कभी एक से अधिक प्रकार का क्षेत्र नहीं रख सकता
    4. सदा उसी समतुल्य स्ट्रक्ट के आकार के ठीक बराबर होता है
    उत्तर देखें

    उत्तर: A — सभी क्षेत्रों को उसी स्मृति पर अध्यारोपित करता है, अपने सबसे बड़े सदस्य के आकार का

    यूनियन का आकार उसके सबसे बड़े सदस्य के बराबर है, क्योंकि हर क्षेत्र वही भंडारण साझा करता है; स्ट्रक्ट का आकार कम-से-कम उसके सभी सदस्यों के आकारों के योग के बराबर है (साथ ही किसी संरेखण-पैडिंग सहित)।
  5. C में, स्ट्रिंग इससे समाप्त होती है:

    1. एक NUL बाइट ('\0')
    2. सदा एक नई-पंक्ति अक्षर
    3. अक्षरों से पहले संचित लंबाई-क्षेत्र
    4. कुछ नहीं; C स्ट्रिंग में कोई समापक नहीं होता
    उत्तर देखें

    उत्तर: A — एक NUL बाइट ('\0')

    लंबाई-उपसर्गित स्ट्रिंग निरूपण के विपरीत, C स्ट्रिंग को शुद्ध रूप से NUL बाइट पर समाप्त होती char सरणी के रूप में निरूपित करता है, और हर मानक स्ट्रिंग-फलन यह जानने हेतु उस बाइट को खोजता है कि स्ट्रिंग कहाँ समाप्त होती है।
  6. main(int argc, char *argv[]) में, argv[0] रखता है:

    1. प्रोग्राम का अपना नाम/आमंत्रण-पथ
    2. पहला उपयोगकर्ता-प्रदत्त आर्गुमेंट
    3. सदा NULL
    4. कुल आर्गुमेंट-गिनती
    उत्तर देखें

    उत्तर: A — प्रोग्राम का अपना नाम/आमंत्रण-पथ

    argc में प्रोग्राम का अपना नाम भी आर्गुमेंटों में गिना जाता है, अतः argv[0] वही नाम/पथ है और पहला वास्तविक उपयोगकर्ता-प्रदत्त आर्गुमेंट argv[1] है — argv पर चलने वाले लूपों में एक आम ऑफ़-बाई-वन जाल।
  7. निम्नलिखित में कौन C में मानक गतिक-स्मृति बग हैं? (एक से अधिक विकल्प सही हो सकते हैं।)

    1. स्मृति-रिसाव — आवंटित स्मृति मुक्त करना भूलना
    2. लटकता संकेतक — मुक्त हो चुकी स्मृति का प्रयोग
    3. द्वि-मुक्ति — उसी खंड को दो बार मुक्त करना
    4. स्थैतिक टाइपिंग — संकलन-समय पर चर का प्रकार घोषित करना
    उत्तर देखें

    उत्तर: A — स्मृति-रिसाव — आवंटित स्मृति मुक्त करना भूलना; B — लटकता संकेतक — मुक्त हो चुकी स्मृति का प्रयोग; C — द्वि-मुक्ति — उसी खंड को दो बार मुक्त करना

    स्मृति-रिसाव, लटकते संकेतक व द्वि-मुक्ति मैनुअल-स्मृति-प्रबंधन के तीन मानक बग हैं; स्थैतिक टाइपिंग C के प्रकार-तंत्र की एक असंबद्ध, सामान्य व सही विशेषता है, बग नहीं।
  8. किसी मैक्रो के पहले से परिभाषित न होने पर ही सशर्त रूप से कोड संकलित करने हेतु प्रयुक्त प्रीप्रोसेसर निर्देश #ifndef है, या वैकल्पिक रूप से #if !defined(...)। इन दो निर्देश-कीवर्डों में से छोटे का नाम बताइए (अग्रणी # के बिना)।

    संख्यात्मक उत्तर — मान टाइप करें।

    उत्तर देखें

    उत्तर: ifndef

    #ifndef ("यदि परिभाषित नहीं") हैडर-गार्ड हेतु प्रयुक्त मानक निर्देश है, जो किसी हैडर की विषय-वस्तु को उसी अनुवाद-इकाई में एक से अधिक बार सम्मिलित होने से रोकता है।