2010-03-27 18 views
59

ओएस: लिनक्स, भाषा: शुद्ध सीprintf विसंगति "कांटा()"

मैं एक विशेष मामले में यूनिक्स के तहत सी सामान्य रूप में प्रोग्रामिंग, और सी प्रोग्रामिंग सीखने में आगे बढ़ने कर रहा हूँ।

मुझे fork() कॉल का उपयोग करने के बाद printf() फ़ंक्शन के एक अजीब (मेरे लिए) व्यवहार का पता चला।

कोड

#include <stdio.h> 
#include <system.h> 

int main() 
{ 
    int pid; 
    printf("Hello, my pid is %d", getpid()); 

    pid = fork(); 
    if(pid == 0) 
    { 
      printf("\nI was forked! :D"); 
      sleep(3); 
    } 
    else 
    { 
      waitpid(pid, NULL, 0); 
      printf("\n%d was forked!", pid); 
    } 
    return 0; 
} 

आउटपुट

Hello, my pid is 1111 
I was forked! :DHello, my pid is 1111 
2222 was forked! 

क्यों दूसरा "हैलो" स्ट्रिंग बच्चे के उत्पादन में कब हुई?

हां, यह माता-पिता के pid के साथ शुरू होने पर मूल रूप से मुद्रित होता है।

लेकिन!

#include <stdio.h> 
#include <system.h> 

int main() 
{ 
    int pid; 
    printf("Hello, my pid is %d\n", getpid()); // SIC!! 

    pid = fork(); 
    if(pid == 0) 
    { 
      printf("I was forked! :D"); // removed the '\n', no matter 
      sleep(3); 
    } 
    else 
    { 
      waitpid(pid, NULL, 0); 
      printf("\n%d was forked!", pid); 
    } 
    return 0; 
} 

आउटपुट:

Hello, my pid is 1111 
I was forked! :D 
2222 was forked! 

यह क्यों होता है कि हम एक स्ट्रिंग के अंत में एक \n चरित्र जगह अगर हम उम्मीद आउटपुट प्राप्त? क्या यह सही व्यवहार है, या यह एक बग है?

उत्तर

75

मुझे लगता है कि <system.h> एक गैर-मानक शीर्षलेख है; मैंने इसे <unistd.h> के साथ बदल दिया और कोड को संकलित रूप से संकलित किया गया।

जब आपके प्रोग्राम का आउटपुट टर्मिनल (स्क्रीन) पर जा रहा है, तो यह लाइन buffered है। जब आपके प्रोग्राम का आउटपुट पाइप पर जाता है, तो यह पूरी तरह से बफर किया जाता है। आप मानक सी फ़ंक्शन setvbuf() और _IOFBF (पूर्ण बफरिंग), _IOLBF (लाइन बफरिंग) और _IONBF (कोई बफरिंग) मोड द्वारा बफरिंग मोड को नियंत्रित कर सकते हैं।

आप अपने प्रोग्राम के आउटपुट को पाइप करके cat पर अपने संशोधित कार्यक्रम में इसे प्रदर्शित कर सकते हैं। printf() तारों के अंत में नई लाइनों के साथ भी, आपको दोहरी जानकारी दिखाई देगी। यदि आप इसे टर्मिनल पर सीधे भेजते हैं, तो आपको केवल एक ही जानकारी दिखाई देगी।

कहानी का नैतिक fflush(0); को कॉल करने से पहले सभी I/O बफर खाली करने के लिए सावधान रहना है।


लाइन-दर-पंक्ति विश्लेषण, अनुरोध किया (ब्रेसिज़ आदि हटा दिया है - और प्रमुख रिक्त स्थान मार्कअप संपादक द्वारा हटा दिया):

  1. printf("Hello, my pid is %d", getpid());
  2. pid = fork();
  3. if(pid == 0)
  4. printf("\nI was forked! :D");
  5. sleep(3);
  6. else
  7. waitpid(pid, NULL, 0);
  8. printf("\n%d was forked!", pid);

विश्लेषण:

  1. प्रतियां मानक आउटपुट के लिए बफर में "नमस्ते, मेरा पीआईडी ​​1234"। क्योंकि अंत में कोई नई लाइन नहीं है और आउटपुट लाइन-बफर मोड (या पूर्ण-बफर मोड) में चल रहा है, टर्मिनल पर कुछ भी दिखाई नहीं देता है।
  2. हमें दो अलग-अलग प्रक्रियाएं देता है, जिसमें स्टडआउट बफर में बिल्कुल वही सामग्री होती है।
  3. बच्चे के पास pid == 0 है और रेखा 4 और 5 निष्पादित करता है; माता-पिता के पास pid (दो प्रक्रियाओं के बीच कुछ अंतरों में से एक के लिए गैर-शून्य मान है - getpid() और getppid() से वापसी मान दो और हैं)।
  4. एक नई लाइन जोड़ता है और "मुझे फोर्क किया गया था!: डी" बच्चे के आउटपुट बफर में। आउटपुट की पहली पंक्ति टर्मिनल पर दिखाई देती है; शेष बफर में आयोजित किया जाता है क्योंकि आउटपुट लाइन buffered है।
  5. सब कुछ 3 सेकंड के लिए रुकता है। इसके बाद, बच्चे मुख्य रूप से मुख्य के अंत में वापसी के माध्यम से बाहर निकलता है। उस बिंदु पर, stdout बफर में अवशिष्ट डेटा flushed है। यह लाइन के अंत में आउटपुट स्थिति छोड़ देता है क्योंकि कोई नई लाइन नहीं है।
  6. माता-पिता यहां आते हैं।
  7. माता-पिता बच्चे को मरने के लिए इंतजार कर रहे हैं।
  8. अभिभावक एक नई लाइन जोड़ता है और "1345 फोर्क किया गया था!" आउटपुट बफर के लिए। बच्चे द्वारा उत्पन्न अधूरी रेखा के बाद, न्यूलाइन आउटपुट में 'हैलो' संदेश फ़्लश करती है।

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

मैं क्या देखा है:

Osiris-2 JL: ./xx 
Hello, my pid is 37290 
I was forked! :DHello, my pid is 37290 
37291 was forked!Osiris-2 JL: 
Osiris-2 JL: 

पीआईडी ​​संख्या अलग हैं - लेकिन समग्र रूप में स्पष्ट है।

#include <stdio.h> 
#include <unistd.h> 

int main() 
{ 
    int pid; 
    printf("Hello, my pid is %d\n", getpid()); 

    pid = fork(); 
    if(pid == 0) 
     printf("I was forked! :D %d\n", getpid()); 
    else 
    { 
     waitpid(pid, NULL, 0); 
     printf("%d was forked!\n", pid); 
    } 
    return 0; 
} 

मैं अब मिल: printf() बयान (जो मानक अभ्यास बहुत जल्दी हो जाता है) के अंत तक नई-पंक्तियों को जोड़ने से उत्पादन एक बहुत बदल

Osiris-2 JL: ./xx 
Hello, my pid is 37589 
I was forked! :D 37590 
37590 was forked! 
Osiris-2 JL: ./xx | cat 
Hello, my pid is 37594 
I was forked! :D 37596 
Hello, my pid is 37594 
37596 was forked! 
Osiris-2 JL: 

सूचना है कि जब उत्पादन टर्मिनल के लिए चला जाता है , यह लाइन-बफर किया गया है, इसलिए 'हैलो' लाइन fork() से पहले दिखाई देती है और केवल एक प्रति थी। जब आउटपुट cat पर पाइप किया जाता है, तो यह पूरी तरह से बफर किया जाता है, इसलिए fork() से पहले कुछ भी नहीं दिखाई देता है और दोनों प्रक्रियाओं में बफर में 'हैलो' लाइन फ्लेश हो जाती है।

+0

ठीक है, मुझे मिल गया। लेकिन मैं अभी भी खुद को समझा नहीं सकता कि बच्चे के आउटपुट में नई मुद्रित रेखा के अंत में "बफर कचरा" क्यों दिखाई देता है? लेकिन प्रतीक्षा करें, अब मुझे संदेह है कि यह वास्तव में बच्चे का आउटपुट है .. ओह, क्या आप समझा सकते हैं कि उत्पादन वास्तव में क्यों दिखता है (पुराने से पहले नई स्ट्रिंग), चरण-दर-चरण, इसलिए मैं बहुत आभारी रहूंगा। वैसे भी धन्यवाद! – pechenie

+0

बहुत प्रभावशाली स्पष्टीकरण! बहुत बहुत धन्यवाद, अंततः मैं इसे स्पष्ट रूप से समझ गया! पीएस .: मैंने आपके लिए पहले वोट दिया था, और अब मैं बेवकूफ रूप से "ऊपर तीर" पर क्लिक करता हूं, इसलिए वोट गायब हो गया। लेकिन "जवाब बहुत पुराना है" के कारण मैं इसे आपको एक बार फिर नहीं दे सकता :( पीपीएस .: मैंने आपको अन्य प्रश्न में वोट दिया। और एक बार फिर धन्यवाद! – pechenie

22

कारण यह है कि प्रारूप स्ट्रिंग के अंत में \n के बिना यह मान तुरंत स्क्रीन पर मुद्रित नहीं होता है। इसके बजाय यह प्रक्रिया के भीतर buffered है। इसका मतलब है कि यह वास्तव में कांटा ऑपरेशन के बाद तक मुद्रित नहीं होता है, इसलिए आप इसे दो बार मुद्रित करते हैं।

\n जोड़ना हालांकि बफर को फ़्लश करने और स्क्रीन पर आउटपुट करने के लिए मजबूर करता है। यह कांटा से पहले होता है और इसलिए केवल एक बार मुद्रित होता है।

आप fflush विधि का उपयोग कर ऐसा करने के लिए मजबूर कर सकते हैं। उदाहरण के लिए

printf("Hello, my pid is %d", getpid()); 
fflush(stdout); 
+0

आपके उत्तर के लिए धन्यवाद! – pechenie

+0

'fflush (stdout); 'इमो को और अधिक सही उत्तर लगता है। –

5

fork() प्रभावी रूप से प्रक्रिया की एक प्रति बनाता है। यदि, fork() पर कॉल करने से पहले, इसमें डेटा था जो buffered था, दोनों माता-पिता और बच्चे के पास एक ही buffered डेटा होगा। अगली बार जब उनमें से प्रत्येक अपने बफर को फ्लश करने के लिए कुछ करता है (जैसे टर्मिनल आउटपुट के मामले में एक नई लाइन प्रिंट करना) तो आप उस प्रक्रिया द्वारा उत्पादित किसी भी नए आउटपुट के अलावा बफर आउटपुट देखेंगे। तो यदि आप माता-पिता और बच्चे दोनों में stdio का उपयोग करने जा रहे हैं तो आपको यह सुनिश्चित करने के लिए fflush होना चाहिए, यह सुनिश्चित करने के लिए कि कोई buffered डेटा नहीं है।

अक्सर, बच्चे का उपयोग केवल exec* फ़ंक्शन पर कॉल करने के लिए किया जाता है। चूंकि यह पूरी बाल प्रक्रिया छवि (किसी भी बफर समेत) को प्रतिस्थापित करता है, इसलिए तकनीकी रूप से fflush की आवश्यकता नहीं है यदि यह वास्तव में वह है जो आप बच्चे में करने जा रहे हैं। हालांकि, यदि डेटा buffered किया जा सकता है तो आपको सावधान रहना चाहिए कि निष्पादन विफलता कैसे संभाली जाती है। विशेष रूप से, किसी भी stdio फ़ंक्शन का उपयोग करके stdout या stderr को त्रुटि प्रिंट करने से बचें (write ठीक है), और उसके बाद (या _Exit) पर कॉल करें, exit पर कॉल करने या बस लौटने (जो किसी भी buffered आउटपुट को फ्लश करेगा) को कॉल करने के बजाय कॉल करें। या फोर्किंग से पहले फ्लश करके पूरी तरह से इस मुद्दे से बचें।

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