यह एक मुख्य () के बिना चलने के लिए सी कोड के दावे को मानता है, लेकिन यह वास्तव में क्या करता है?


84
#include <stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)

int begin()
{
    printf("Ha HA see how it is?? ");
}

क्या यह अप्रत्यक्ष रूप से कॉल करता है main? कैसे?


146
मैक्रोज़ परिभाषित विस्तार को "मुख्य" कहना शुरू करते हैं। यह सिर्फ एक चाल है। कुछ खास दिलचस्प नहीं।
रगोम

10
आपके टूलकिन में फ़ाइल में प्रीप्रोसेड कोड को छोड़ने का विकल्प होना चाहिए - वास्तविक फ़ाइल जो संकलित है - जहां आप इसे देखेंगे, वास्तव में, इसका एक मुख्य () है

@rghome उत्तर के रूप में पोस्ट क्यों नहीं? और यह स्पष्ट रूप से दिलचस्प है, ऊपर की संख्या को देखते हुए।
मात्सेमन

3
@ मत्स्यमन्त्र वाह! मैंने अप-वोट को नोटिस नहीं किया। मैं इसे एक उत्तर में बदल सकता था, और यदि टिप्पणी अप-वोट उत्तर-वोट थे, तो यह मेरा सबसे अच्छा स्कोर होगा, लेकिन पहले से ही एक विस्तृत प्रतिक्रिया है। मुझे लगता है कि मेरी टिप्पणी की बात यह है कि यह वास्तव में दिलचस्प नहीं है और इसलिए यह उन लोगों के लिए एक विकल्प के रूप में कार्य करता है जो उत्तर को वोट नहीं देना चाहते हैं। हालांकि यह इंगित करने के लिए धन्यवाद।
रगोम

दोस्तों, यह लिंक करने वाले के लिए एक ऑपरेटिंग सिस्टम टूल के रूप में है जो एंट्री पॉइंट सेट करता है, और भाषा ही नहीं। तुम भी हमारे प्रवेश बिंदु सेट कर सकते हैं, और आप एक पुस्तकालय बना सकते हैं जो निष्पादन योग्य भी है! unix.stackexchange.com/a/223415/37799
Ho1

जवाबों:


193

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

आपके मामले में, mainप्रीप्रोसेसर परिभाषाओं द्वारा छिपा हुआ है। जिसका begin()विस्तार decode(a,n,i,m,a,t,e)आगे तक किया जाएगा main

int begin() -> int decode(a,n,i,m,a,t,e)() -> int m##a##i##n() -> int main() 

decode(s,t,u,m,p,e,d)7 मापदंडों के साथ एक पैरामीटर मैक्रो है। इस मैक्रो के लिए प्रतिस्थापन सूची है m##s##u##tm, s, uऔर प्रतिस्थापन सूची में t4 वें , 1 सेंट , 3 आरडी और 2 एन डी पैरामीटर का उपयोग किया जाता है।

s, t, u, m, p, e, d
1  2  3  4  5  6  7

बाकी का कोई फायदा नहीं है ( सिर्फ आपत्ति करने के लिए )। तर्क दिया गया decode" , एन , आई , एम , ए, टी, ई" है, इसलिए, पहचानकर्ता m, s, uऔर क्रमशः tतर्कों m, a, iऔर के साथ बदल दिए जाते nहैं।

 m --> m  
 s --> a 
 u --> i 
 t --> n

11
@GrijeshChauhan सभी सी संकलक मैक्रोज़ की प्रक्रिया करते हैं, यह C89 के बाद से सभी C मानकों द्वारा आवश्यक है।
jdarthenay

17
यह स्पष्ट रूप से गलत है। लिनक्स पर मैं उपयोग कर सकते हैं _start()। या इससे भी अधिक निम्न-स्तर मैं अपने कार्यक्रम की शुरुआत को उस पते से संरेखित करने का प्रयास कर सकता हूं जिसमें बूट के बाद आईपी सेट किया गया है। main()C मानक पुस्तकालय है । सी खुद इस पर प्रतिबंध नहीं लगाता है।
ljrk

1
@ झटके मानक पुस्तकालय एक प्रविष्टि बिंदु को परिभाषित करता है। भाषा की परवाह नहीं है
ljrk

3
क्या आप बता सकते हैं कि कैसे decode(a,n,i,m,a,t,e)बनते हैं m##a##i##n? क्या यह पात्रों को प्रतिस्थापित करता है? क्या आप decodeफ़ंक्शन के प्रलेखन के लिए एक लिंक प्रदान कर सकते हैं ? धन्यवाद।
AL

1
@AL पहले beginपरिभाषित किया गया है decode(a,n,i,m,a,t,e)जिसके द्वारा परिभाषित किया गया है । यह फ़ंक्शन तर्कों को लेता है s,t,u,m,p,e,dऔर उन्हें इस रूप में समेटता है m##s##u##t( ##मतलब समवर्ती)। यानी, यह पी, ई और डी के मूल्यों की उपेक्षा करता है। जैसा कि आप decodeएस = ए, टी = एन, यू = आई, एम = एम के साथ "कॉल" करते हैं, यह प्रभावी रूप से बदलता beginहै main
ljrk

71

प्रयोग करके देखें gcc -E source.c, आउटपुट समाप्त होता है:

int main()
{
    printf("Ha HA see how it is?? ");
}

तो एक main()फ़ंक्शन वास्तव में प्रीप्रोसेसर द्वारा उत्पन्न होता है।


37

विचाराधीन कार्यक्रम मैक्रो विस्तार के कारण कॉल करता हैmain() , लेकिन आपकी धारणा त्रुटिपूर्ण है - इसे कॉल करने की आवश्यकता नहीं है main()!

सख्ती से बोलना, आपके पास एक सी कार्यक्रम हो सकता है और एक mainप्रतीक के बिना इसे संकलित करने में सक्षम हो सकता है । mainयह एक ऐसी चीज है c library, जिसमें अपनी खुद की इनिशियलाइज़ेशन को पूरा करने के बाद कूदने की उम्मीद है। आमतौर पर आप mainlibc प्रतीक से कूदते हैं जिसे जाना जाता है _start। यह हमेशा एक बहुत ही वैध कार्यक्रम संभव है, कि बस एक मुख्य होने के बिना, विधानसभा को निष्पादित करता है। इस पर एक नजर डालिए:

/* This must be compiled with the flag -nostdlib because otherwise the
 * linker will complain about multiple definitions of the symbol _start
 * (one here and one in glibc) and a missing reference to symbol main
 * (that the libc expects to be linked against).
 */

void
_start ()
{
    /* calling the write system call, with the arguments in this order:
     * 1. the stdout file descriptor
     * 2. the buffer we want to print (Here it's just a string literal).
     * 3. the amount of bytes we want to write.
     */
    asm ("int $0x80"::"a"(4), "b"(1), "c"("Hello world!\n"), "d"(13));
    asm ("int $0x80"::"a"(1), "b"(0)); /* calling exit syscall, with the argument to be 0 */
}

उपरोक्त के साथ संकलित करें gcc -nostdlib without_main.c, और इसे Hello World!इनलाइन असेंबली में सिस्टम कॉल (इंटरप्ट) जारी करके स्क्रीन पर प्रिंट करते हुए देखें ।

इस विशेष मुद्दे के बारे में अधिक जानकारी के लिए, ksplice ब्लॉग देखें

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

/* These values are extracted from the decimal representation of the instructions
 * of a hello world program written in asm, that gdb provides.
 */
const int main[] = {
    -443987883, 440, 113408, -1922629632,
    4149, 899584, 84869120, 15544,
    266023168, 1818576901, 1461743468, 1684828783,
    -1017312735
};

ऐरे में मान बाइट्स हैं जो स्क्रीन पर हैलो वर्ल्ड को प्रिंट करने के लिए आवश्यक निर्देशों के अनुरूप हैं। यह विशिष्ट कार्यक्रम कैसे काम करता है, इस बारे में अधिक जानकारी के लिए, इस ब्लॉग पोस्ट पर एक नज़र डालें , जहाँ मैं इसे पहले भी पढ़ता हूँ।

मैं इन कार्यक्रमों के बारे में एक अंतिम सूचना देना चाहता हूं। मुझे नहीं पता कि वे सी भाषा विनिर्देश के अनुसार वैध सी कार्यक्रमों के रूप में पंजीकृत हैं, लेकिन उन्हें संकलित करना और उन्हें चलाना निश्चित रूप से बहुत संभव है, भले ही वे विनिर्देश का उल्लंघन करते हों।


1
क्या _startएक परिभाषित मानक के हिस्से का नाम है , या यह केवल कार्यान्वयन-विशिष्ट है? निश्चित रूप से आपका "एक सरणी के रूप में मुख्य" वास्तुकला-विशिष्ट है। यह भी महत्वपूर्ण है, सुरक्षा प्रतिबंधों के कारण रन टाइम में विफल होने के लिए यह आपके "मुख्य एक सरणी" चाल के रूप में अनुचित नहीं होगा (हालांकि यह अधिक संभावना होगी यदि आपने constक्वालीफायर का उपयोग नहीं किया है , और अभी भी कई सिस्टम इसे अनुमति देंगे)।
महापर्व

1
@mah: _startELF मानक में, हालांकि AMD64 psABI का संदर्भ होता है नहीं है _startपर 3.4 प्रक्रिया प्रारंभ । आधिकारिक तौर पर, ईएलएफ केवल e_entryईएलएफ हेडर में पते के बारे में जानता है , _startकेवल एक नाम है जिसे कार्यान्वयन ने चुना है।
नवजाल

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

1
@ स्टीवर्ट: यह निश्चित रूप से ARMv6l (विभाजन दोष) पर विफल रहता है। लेकिन यह किसी भी x86-64 आर्किटेक्चर पर काम करना चाहिए।
१६:३६ पर लेफ्टरेंबाउट left

@AndrewHenle एक द्विआधारी निष्पादन योग्य एक द्विआधारी निष्पादन योग्य है, चाहे वह वहां कैसे भी हो - बिल्कुल सच नहीं है। एक द्विआधारी निष्पादन योग्य निष्पादन योग्य निर्देशों का एक भी बूँद नहीं है, यह विभाजन का सावधानीपूर्वक मानचित्रण बूँद है, जिनमें से कुछ निर्देश हैं, जिनमें से कुछ केवल-पढ़ने के लिए डेटा हैं, और जिनमें से कुछ डेटा को रीड-राइट डेटा में आरंभीकृत किया जाता है। (कुछ) सुरक्षा हार्डवेयर MMUs ऐसे पृष्ठों के रूप में चिह्नित नहीं होने से निष्पादन को रोक सकते हैं, और यह रोकने के लिए एक अच्छी सुविधा है, उदाहरण के लिए, स्टैक पर कोड निष्पादित करने के लिए अग्रणी overflows लेकिन दुख की बात है कि कभी-कभी वैध या अक्सर सक्षम नहीं होता है।
महिंद्रा

30

कोई जादूगर की तरह काम करने की कोशिश कर रहा है। वह सोचता है कि वह हमें बरगला सकता है। लेकिन हम सभी जानते हैं, सी कार्यक्रम निष्पादन के साथ शुरू होता है main()

int begin()साथ प्रतिस्थापित किया जाएगा decode(a,n,i,m,a,t,e)पूर्वप्रक्रमक मंच पर एक पास से। फिर फिर से, decode(a,n,i,m,a,t,e)एम ## ए ## मैं ## एन के साथ बदल दिया जाएगा। मैक्रो कॉल की स्थिति के अनुसार, sचरित्र का एक मूल्य होगा a। इसी तरह, u'i' tसे बदलकर 'n' से बदल दिया जाएगा। और, यह कैसे है, m##s##u##tबन जाएगाmain

##मैक्रो विस्तार में प्रतीक के बारे में, यह प्रीप्रोसेसिंग ऑपरेटर है और यह टोकन चिपकाने का काम करता है। जब एक मैक्रो का विस्तार किया जाता है, तो प्रत्येक '##' ऑपरेटर के दोनों ओर के दो टोकन एक एकल टोकन में जुड़ जाते हैं, जो फिर '##' और दो मूल टोकन को मैक्रो विस्तार में बदल देता है।

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

gcc -E FILENAME.c

11

decode(a,b,c,d,[...])पहले चार तर्कों को फेरबदल करता है और क्रम में एक नया पहचानकर्ता प्राप्त करने के लिए उन्हें जोड़ता है dacb। (शेष तीन तर्कों की अनदेखी की जाती है।) उदाहरण के लिए, decode(a,n,i,m,[...])पहचानकर्ता देता है main। ध्यान दें कि यह वही है जिसे beginमैक्रो के रूप में परिभाषित किया गया है।

इसलिए, beginमैक्रो को बस के रूप में परिभाषित किया गया है main


2

आपके उदाहरण में, main()फ़ंक्शन वास्तव में मौजूद है, क्योंकि beginएक मैक्रो है जो कंपाइलर decodeमैक्रो के साथ बदलता है जो बदले में अभिव्यक्ति m ## s ## u ## t द्वारा प्रतिस्थापित किया जाता है। मैक्रो विस्तार का उपयोग करके ##, आप शब्द mainसे पहुंचेंगे decode। यह एक ट्रेस है:

begin --> decode(a,n,i,m,a,t,e) --> m##parameter1##parameter3##parameter2 ---> main

यह सिर्फ करने के लिए एक चाल है main(), लेकिन main()प्रोग्राम की प्रविष्टि फ़ंक्शन के लिए नाम का उपयोग करना सी प्रोग्रामिंग भाषा में आवश्यक नहीं है। यह आपके ऑपरेटिंग सिस्टम और लिंकर पर निर्भर करता है।

Windows में, आप हमेशा उपयोग नहीं करते main()हैं, लेकिन बल्कि WinMainयाwWinMain , हालांकि आप उपयोग कर सकते हैं main(), यहां तक कि माइक्रोसॉफ्ट के toolchain के साथ । लिनक्स में, कोई भी उपयोग कर सकता है _start

यह प्रवेश बिंदु को सेट करने के लिए एक ऑपरेटिंग सिस्टम टूल के रूप में लिंकर पर निर्भर है, और भाषा ही नहीं। तुम भी हमारे प्रवेश बिंदु सेट कर सकते हैं, और आप एक पुस्तकालय बना सकते हैं जो निष्पादन योग्य भी है !


@vaxquis आप सही हैं, लेकिन यह एक आंशिक उत्तर है, जिसे मैंने पहला उत्तर main()दिया है, जो C प्रोग्रामिंग लैंग्वेज के फंक्शन को बांधता है , जो कि सही नहीं है।
1

@vaxquis मैंने मान लिया कि "मुख्य कार्यक्रमों (सी प्रोग्राम्स में फ़ंक्शन आवश्यक नहीं है) की व्याख्या करना एक आंशिक उत्तर होगा।" मैंने उत्तर को पूर्ण बनाने के लिए एक अनुच्छेद जोड़ा है। - Ho1 16 मिनट पहले
Ho1
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.