16

क्या सी प्रोग्राम में प्रवेश बिंदु (मुख्य) से बचना संभव है। नीचे दिए गए कोड में, क्या नीचे दिए गए प्रोग्राम में main() के माध्यम से कॉल किए बिना func() कॉल को आमंत्रित करना संभव है? यदि हां, यह कैसे करें और इसकी आवश्यकता कब होगी और ऐसा प्रावधान क्यों दिया जाता है?सी प्रोग्राम में मुख्य (प्रवेश बिंदु) से बचें

int func(void) 
{ 
    printf("This is func \n"); 
    return 0; 
} 

int main(void) 
{ 
    printf("This is main \n"); 
    return 0; 
} 
+9

आपको ऐसा करने की आवश्यकता क्यों होगी? – Oded

+2

सी ++ में वैश्विक स्थिर वस्तु का सीटीआर मुख्य() से पहले चलाया जा सकता है। – seand

+0

ओडेड के प्रश्न को दोबारा शुरू करने के लिए: हमें बताएं कि आप * प्राप्त करना चाहते हैं * और हम आपको बताएंगे कि इसे कैसे प्राप्त किया जाए, शायद 'मुख्य' को बाधित किए बिना। (अधिक विशेष रूप से: कुछ बेहतर है। सी ज्ञान की मेरी कमी मुझे आपकी मदद करने से रोकती है।) – MvanGeest

उत्तर

22

यदि आप जीसीसी का उपयोग कर रहे हैं, तो मुझे एक धागा मिला है जिसमें कहा गया है कि आप एक अलग प्रविष्टि बिंदु निर्दिष्ट करने के लिए -e command-line parameter का उपयोग कर सकते हैं; इसलिए आप func का उपयोग अपने एंट्री पॉइंट के रूप में कर सकते हैं, जो main अप्रयुक्त हो जाएगा।

ध्यान दें कि यह वास्तव में आपको main की बजाय एक और दिनचर्या नहीं कहने देता है। इसके बजाए, यह आपको _start की बजाय एक और दिनचर्या कॉल करने देता है, जो libc स्टार्टअप दिनचर्या है - यह कुछ सेटअप करता है और फिर यह कॉल main पर कॉल करता है। तो यदि आप ऐसा करते हैं, तो आप अपने प्रारंभिक कोड को खो देंगे जो आपके रनटाइम लाइब्रेरी में बनाया गया है, जिसमें पार्सिंग कमांड लाइन तर्क जैसे चीजें शामिल हो सकती हैं। इसका उपयोग करने से पहले इस पैरामीटर पर पढ़ें।

यदि आप किसी अन्य कंपाइलर का उपयोग कर रहे हैं, तो इसके लिए पैरामीटर हो सकता है या नहीं भी हो सकता है।

+0

दिलचस्प जानकारी। उसके लिए + 1। क्या यह विभिन्न प्रविष्टि बिंदु की उपस्थिति निर्धारित करने के प्रावधान भी प्रदान करता है? –

+0

शायद नहीं, क्योंकि वह पैरामीटर लिंकर को प्रभावित करता है, संकलक नहीं। लेकिन आपको इसका पता लगाने की आवश्यकता क्यों होगी? आप वह व्यक्ति हैं जो आपके ऐप को संकलित कर रहा है, इसलिए आप जान लेंगे कि आप इसे इस तरह से बना रहे हैं या नहीं। –

0

यह वास्तव में निर्भर करता है कि कैसे आप द्विआधारी लागू कर रहे हैं, और यथोचित मंच और पर्यावरण विशिष्ट होने जा रहा है। सबसे स्पष्ट उत्तर यह है कि किसी अन्य चीज़ के लिए "मुख्य" प्रतीक का नाम बदलें और "func" "मुख्य" पर कॉल करें, लेकिन मुझे संदेह है कि आप ऐसा करने की कोशिश नहीं कर रहे हैं।

5

अंगूठे का एक नियम यह होगा कि सिस्टम द्वारा प्रदान किया गया लोडर हमेशा रन मुख्य होगा। पर्याप्त प्राधिकरण और योग्यता के साथ आप सैद्धांतिक रूप से एक अलग लोडर लिख सकते हैं जिसने कुछ और किया।

3

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

हालांकि एक आसान तरीका है कि आप एक स्थिर पुस्तकालय में 'छुपाएं' मुख्य() को अपने कोड से लिंक करें। मुख्य के क्रियान्वयन() की तरह दिखाई देता है:

int main(void) 
{ 
    func() ; 
} 

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

16

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

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

जब सी लोड और शुरू में लिखा गया एक प्रोग्राम, कुछ घटक पर्यावरण बनाने के लिए ज़िम्मेदार है जिसमें main() मौजूद है। यूनिक्स, लिनक्स, विंडोज, और अन्य इंटरैक्टिव वातावरण में, उस प्रयास में से अधिकांश ओएस घटक का एक प्राकृतिक परिणाम है जो प्रोग्राम को लोड करता है। हालांकि, यहां तक ​​कि इन वातावरणों में main() से पहले शुरू करने के लिए प्रारंभिक कार्य की कुछ मात्रा भी कहा जा सकता है। यदि कोड वास्तव में सी ++ है, तो वहां पर्याप्त मात्रा में काम हो सकता है जिसमें सभी वैश्विक ऑब्जेक्ट उदाहरणों के लिए कन्स्ट्रक्टर को कॉल करना शामिल है।

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

संपादित करें: अधिक सामान्य संदर्भ में पूछे गए प्रश्न का अधिक सीधे जवाब देने के लिए: "क्या आप मुख्य के बजाय foo को कॉल कर सकते हैं?" जवाब "शायद, लेकिन केवल मुश्किल होने के द्वारा" है।

विंडोज़ पर, एक निष्पादन योग्य और डीएलएल फ़ाइल के लगभग समान प्रारूप हैं। एक प्रोग्राम लिखना संभव है जो रनटाइम पर नामित मनमानी डीएलएल लोड करता है, और इसके भीतर एक मनमाने ढंग से कार्य करता है, और इसे कॉल करता है। ऐसा एक कार्यक्रम वास्तव में एक मानक विंडोज वितरण के हिस्से के रूप में जहाज: rundll32.exe

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

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

+0

ओपी से अधिक शायद कभी जानना चाहता था, लेकिन किसी भी मामले में आकर्षक पढ़ने! –

+0

@ करल, भले ही आपको ज्ञान का उपयोग करने की आवश्यकता न हो, फिर भी कभी-कभी हुड के नीचे देखने के लिए उपयोगी हो सकता है .... और वास्तव में एक लिंकर को लागू करने का प्रयास बेहद निर्देशक व्यायाम है ;-) – RBerteig

+0

ओह, बिल्कुल! मैंने वास्तव में लगभग 20 साल पहले एक एम्बेडेड सिस्टम के लिए ऐसा कुछ किया था, एक एम्बेडेड सिस्टम के लिए कस्टम फिर से ढूंढने वाले टर्बो सी संकलित कोड। फिर भी, मुझे खुशी है कि मुझे ऐसा करने की ज़रूरत नहीं है :) –

1

समाधान आपके द्वारा उपयोग किए जाने वाले संकलक और लिंकर पर निर्भर करता है। हमेशा यह है कि main एप्लिकेशन का वास्तविक प्रवेश बिंदु है। असली प्रविष्टि बिंदु कुछ प्रारंभिक बनाता है और उदाहरण के लिए main पर कॉल करता है। आप दृश्य स्टूडियो का उपयोग कर Windows के लिए कार्यक्रमों लिखने हैं, तो आप लिंकर की उपयोग कर सकते हैं/प्रवेश स्विच डिफ़ॉल्ट प्रवेश बिंदु mainCRTStartup अधिलेखित करने के लिए और func() बजाय main() फोन:

#ifdef NDEBUG 
void mainCRTStartup() 
{ 
    ExitProcess (func()); 
} 
#endif 

तो एक मानक अभ्यास है अगर आप सबसे अधिक लिखना छोटा आवेदन मामले में आपको सी-रनटाइम कार्यों के उपयोग में प्रतिबंध प्राप्त होंगे। आपको सी-रनटाइम फ़ंक्शन के बजाय विंडोज एपीआई फ़ंक्शन का उपयोग करना चाहिए।printf("This is func \n") के बजाय उदाहरण के लिए आप OutputString(TEXT("This is func \n")) जहां OutputString केवल WriteFile या WriteConsole के संबंध में लागू किया जाता है का उपयोग करना चाहिए:

static HANDLE g_hStdOutput = INVALID_HANDLE_VALUE; 
static BOOL g_bConsoleOutput = TRUE; 

BOOL InitializeStdOutput() 
{ 
    g_hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE); 
    if (g_hStdOutput == INVALID_HANDLE_VALUE) 
     return FALSE; 

    g_bConsoleOutput = (GetFileType (g_hStdOutput) & ~FILE_TYPE_REMOTE) != FILE_TYPE_DISK; 
#ifdef UNICODE 
    if (!g_bConsoleOutput && GetFileSize (g_hStdOutput, NULL) == 0) { 
     DWORD n; 

     WriteFile (g_hStdOutput, "\xFF\xFE", 2, &n, NULL); 
    } 
#endif 

    return TRUE; 
} 

void Output (LPCTSTR pszString, UINT uStringLength) 
{ 
    DWORD n; 

    if (g_bConsoleOutput) { 
#ifdef UNICODE 
     WriteConsole (g_hStdOutput, pszString, uStringLength, &n, NULL); 
#else 
     CHAR szOemString[MAX_PATH]; 
     CharToOem (pszString, szOemString); 
     WriteConsole (g_hStdOutput, szOemString, uStringLength, &n, NULL); 
#endif 
    } 
    else 
#ifdef UNICODE 
     WriteFile (g_hStdOutput, pszString, uStringLength * sizeof (TCHAR), &n, NULL); 
#else 
    { 
     //PSTR pszOemString = _alloca ((uStringLength + sizeof(DWORD))); 
     CHAR szOemString[MAX_PATH]; 
     CharToOem (pszString, szOemString); 
     WriteFile (g_hStdOutput, szOemString, uStringLength, &n, NULL); 
    } 
#endif 
} 

void OutputString (LPCTSTR pszString) 
{ 
    Output (pszString, lstrlen (pszString)); 
} 
4

समारोह और समारोह मुख्य हो सकता है और नाम से समारोह कॉल करने के लिए होने के लिए मुख्य नाम बदलें।

यदि आपके पास स्रोत तक पहुंच है, तो आप यह कर सकते हैं और यह आसान है।