2012-01-16 16 views
8

मैं सी सीखने की कोशिश कर रहा हूं और पहले से ही बहुत उलझन में हूं।विशेष सी फ़ंक्शन कैसे काम करता है?

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

अब सी में मुझे पता है कि यह मामला नहीं है इसलिए मैं निम्नलिखित समस्या को समझ नहीं सकता, printf() कैसे काम करता है।

उदाहरण के लिए:

char chVar = 'A'; 
int intVar = 123; 
float flVar = 99.999; 

printf("%c - %i - %f \n",chVar, intVar, flVar); 
printf("%i - %f - %c \n",intVar, flVar, chVar); 
printf("%f - %c - %i \n",flVar, chVar, intVar); 

अब के रूप में सी does'nt समर्थन समारोह से अधिक भार, कैसे printf किसी भी प्रकार की, तर्क के किसी भी संख्या में लेने के लिए प्रबंधन करता है, और उसके बाद उन लोगों के साथ सही ढंग से काम?

मैंने glibc स्रोत पैकेज डाउनलोड करके printf() को काम करने का प्रयास किया है, लेकिन मुझे लगता है कि मैं इसे देख रहा हूं, हालांकि मुझे लगता है।

क्या कोई यहां समझा सकता है कि सी उपर्युक्त कार्य कैसे करता है?

+0

यदि आप और जानना चाहते हैं, तो कॉलिंग सम्मेलनों और स्टैक –

+6

पर पढ़ना आपका प्रश्न सी के बारे में है, लेकिन आप कहते हैं "मैं सी ++ सीखने की कोशिश कर रहा हूं।" यदि आप सी ++ सीखने की कोशिश कर रहे हैं, तो भाषा के सी हिस्सों से बचने के लिए शुरू करना सबसे अच्छा है (और सी ++ एपॉर्ड्स के अमूर्तता के उच्च स्तर पर बने रहें), फिर एक बार जब आप इसके साथ सहज हों, तो भाषा के अन्य हिस्सों में खोदने के लिए । –

+0

यदि आप इसके बारे में चिंता नहीं करना चाहते हैं तो g ++ के लिए तर्क के रूप में '-फॉर्मैट' को पास करें। अन्य कंपाइलर्स, मुझे लगता है/उम्मीद/उम्मीद है, समान चेतावनियां होनी चाहिए। यह संकलक को जांचने का कारण बनता है कि प्रकार मेल खाते हैं। –

उत्तर

11

सी "varargs" नामक फ़ंक्शन हस्ताक्षर का एक प्रकार का समर्थन करता है जिसका अर्थ है "परिवर्तनीय (संख्याओं) तर्क"। इस तरह के एक समारोह में कम से कम एक आवश्यक तर्क होना चाहिए। printf के मामले में, प्रारूप स्ट्रिंग एक आवश्यक तर्क है।

आम तौर पर, एक स्टैक-आधारित मशीन पर, जब आप किसी भी सी फ़ंक्शन को कॉल करते हैं, तो तर्क दाएं से बाएं से स्टैक पर धकेल जाते हैं। इस तरह, फ़ंक्शन के लिए पहला तर्क है कि रिटर्न पते के ठीक बाद, स्टैक के "शीर्ष" पर पाया जाता है।

सी मैक्रोज़ परिभाषित हैं जो आपको चर तर्कों को पुनर्प्राप्त करने की अनुमति देते हैं।

महत्वपूर्ण बिंदु हैं:

  • चर तर्क के लिए कोई प्रकार- सुरक्षा नहीं है। printf() के मामले में, यदि प्रारूप स्ट्रिंग गलत है, तो कोड स्मृति से अमान्य परिणाम पढ़ सकता है, संभवतः क्रैश हो रहा है।
  • परिवर्तनीय तर्क एक सूचक के माध्यम से पढ़े जाते हैं जो उन तर्कों वाली स्मृति के माध्यम से बढ़ता है।
  • तर्क सूचक को va_start के साथ प्रारंभ किया जाना चाहिए, va_arg के साथ बढ़ाया गया है, और va_end के साथ जारी किया गया है।

मैं तुम्हें संबंधित सवाल पर दिलचस्प लग सकती कोड के लिए ढेर पोस्ट किया है:

Best Way to Store a va_list for Later Use in C/C++

यहाँ एक printf() जो केवल स्वरूपों पूर्णांकों ("% d") की एक कंकाल है:

int printf(const char * fmt, ...) 
{ 
    int d; /* Used to store any int arguments. */ 
    va_list args; /* Used as a pointer to the next variable argument. */ 

    va_start(args, fmt); /* Initialize the pointer to arguments. */ 

    while (*fmt) 
    { 
     if ('%' == *fmt) 
     { 
      fmt ++; 

      switch (*fmt) 
      { 
       case 'd': /* Format string says 'd'. */ 
          /* ASSUME there is an integer at the args pointer. */ 

        d = va_arg(args, int); 
        /* Print the integer stored in d... */ 
        break; 
      } 
     } 
     else 
      /* Not a format character, copy it to output. */ 
     fmt++; 
    } 

    va_end(args); 
} 
4

आंतरिक रूप से, printf (कम से कम आमतौर पर) stdarg.h से कुछ मैक्रोज़ का उपयोग करेगा। सामान्य विचार (की एक व्यापक विस्तार हुआ संस्करण) कुछ इस तरह है:

#include <stdarg.h> 
#include <stdio.h> 
#include <string.h> 

int my_vfprintf(FILE *file, char const *fmt, va_list arg) { 

    int int_temp; 
    char char_temp; 
    char *string_temp; 
    char ch; 
    int length = 0; 

    char buffer[512]; 

    while (ch = *fmt++) { 
     if ('%' == ch) { 
      switch (ch = *fmt++) { 
       /* %% - print out a single % */ 
       case '%': 
        fputc('%', file); 
        length++; 
        break; 

       /* %c: print out a character */ 
       case 'c': 
        char_temp = va_arg(arg, int); 
        fputc(char_temp, file); 
        length++; 
        break; 

       /* %s: print out a string  */ 
       case 's': 
        string_temp = va_arg(arg, char *); 
        fputs(string_temp, file); 
        length += strlen(string_temp); 
        break; 

       /* %d: print out an int   */ 
       case 'd': 
        int_temp = va_arg(arg, int); 
        itoa(int_temp, buffer, 10); 
        fputs(buffer, file); 
        length += strlen(buffer); 
        break; 

       /* %x: print out an int in hex */ 
       case 'x': 
        int_temp = va_arg(arg, int); 
        itoa(int_temp, buffer, 16); 
        fputs(buffer, file); 
        length += strlen(buffer); 
        break; 
      } 
     } 
     else { 
      putc(ch, file); 
      length++; 
     } 
    } 
    return length; 
} 

int my_printf(char const *fmt, ...) { 
    va_list arg; 
    int length; 

    va_start(arg, fmt); 
    length = my_vfprintf(stdout, fmt, arg); 
    va_end(arg); 
    return length; 
} 

int my_fprintf(FILE *file, char const *fmt, ...) { 
    va_list arg; 
    int length; 

    va_start(arg, fmt); 
    length = my_vfprintf(file, fmt, arg); 
    va_end(arg); 
    return length; 
} 


#ifdef TEST 

int main() { 
    my_printf("%s", "Some string"); 
    return 0; 
} 

#endif 

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

+0

यह गलत है .. –

+0

@ हेथहुनिकटुट: शायद आप समझा सकते हैं आपको क्या लगता है गलत है? –

+0

आप उस भाग पर आगे निकल गए जहां एक va_list पहले से ही बनाई जा चुकी है। यह स्वचालित नहीं है, लेकिन आपका जवाब ऐसा प्रतीत होता है। यह हो सकता है (शायद नहीं, लेकिन संभव है कि मैं आपको अनुदान देता हूं), कि printf() my_vprintf() के आस-पास एक रैपर है जैसा आपने उदाहरण दिया था। लेकिन महत्वपूर्ण ज्ञान यह है कि va_start और अंतिम गैर-चरणीय तर्क का उपयोग करके va_list कैसे बनाएं। उस भाग को छोड़ना "गलत" के आदेश पर एक गैर-उत्तर है। –

2

(अगर आप gcc (और g ++?) का उपयोग कर रहे हैं, तो इसे मत भूलें, आप कंपाइलर विकल्पों में -Wformat पास कर सकते हैं ताकि यह जांच सके कि तर्कों के प्रकार स्वरूपण से मेल खाते हैं। मुझे उम्मीद है कि अन्य कंपाइलर्स के समान विकल्प हैं।)

क्या कोई यहां समझा सकता है कि सी उपर्युक्त कार्य कैसे करता है?

अंधविश्वास। यह मानता है कि आपने यह सुनिश्चित किया है कि तर्कों के प्रकार आपकी प्रारूप स्ट्रिंग में संबंधित अक्षरों से पूरी तरह मेल खाते हैं। जब printf कहा जाता है, तो सभी तर्क बाइनरी में प्रदर्शित होते हैं, अनजाने में एक साथ संयोजित होते हैं, और प्रभावी रूप से printf पर एक बड़े तर्क के रूप में प्रभावी रूप से पारित होते हैं। यदि वे मेल नहीं खाते हैं, तो आपको समस्याएं आती हैं। printf स्वरूप स्ट्रिंग के माध्यम से पुनरावृत्त होता है, हर बार जब यह %d देखता है तो यह तर्कों से 4 बाइट लेगा (32-बिट मानते हुए, यह निश्चित रूप से 64-बिट इंट्स के लिए 8 बाइट होगा) और यह उन्हें एक पूर्णांक के रूप में व्याख्या करेगा।

अब शायद आप वास्तव में एक double (एक int के रूप में आम तौर पर लेने के दो बार के रूप में ज्यादा स्मृति), जिस स्थिति में printf सिर्फ उन बिट्स के 32 लेते हैं और उन्हें एक पूर्णांक के रूप में प्रतिनिधित्व होगा पारित कर दिया। फिर अगला प्रारूप फ़ील्ड (शायद %d) शेष डबल ले जाएगा।

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

संबंधित मुद्दे