यह C फ़ंक्शन हमेशा गलत होना चाहिए, लेकिन ऐसा नहीं है


317

मैं बहुत समय पहले एक मंच पर एक दिलचस्प सवाल पर ठोकर खाई थी और मैं इसका जवाब जानना चाहता हूं।

निम्नलिखित सी फ़ंक्शन पर विचार करें:

f1.c

#include <stdbool.h>

bool f1()
{
    int var1 = 1000;
    int var2 = 2000;
    int var3 = var1 + var2;
    return (var3 == 0) ? true : false;
}

इसके falseबाद से हमेशा लौटना चाहिए var3 == 3000mainसमारोह इस तरह दिखता है:

main.c

#include <stdio.h>
#include <stdbool.h>

int main()
{
    printf( f1() == true ? "true\n" : "false\n");
    if( f1() )
    {
        printf("executed\n");
    }
    return 0;
}

चूँकि f1()हमेशा वापस आना चाहिए false, इसलिए किसी को यह उम्मीद होगी कि कार्यक्रम स्क्रीन पर केवल एक ही गलत प्रिंट करेगा । लेकिन इसे संकलित करने और चलाने के बाद, निष्पादित भी प्रदर्शित किया जाता है:

$ gcc main.c f1.c -o test
$ ./test
false
executed

ऐसा क्यों है? क्या इस कोड में किसी प्रकार का अपरिभाषित व्यवहार है?

नोट: मैंने इसे संकलित किया है gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2


9
दूसरों ने उल्लेख किया है कि आपको एक प्रोटोटाइप की आवश्यकता है क्योंकि आपके कार्य अलग-अलग फाइलों में हैं। लेकिन फिर भी यदि आप f1()उसी फ़ाइल में कॉपी करते हैं main(), तो आपको कुछ अजीबता मिलेगी: जबकि यह C ++ में सही ()पैरामीटर सूची के लिए उपयोग करने के लिए सही है, सी में एक फ़ंक्शन के लिए उपयोग किया जाता है, जो अभी तक परिभाषित पैरामीटर सूची के साथ नहीं है ( यह मूल रूप से )) के बाद एक कश्मीर और आर-शैली पैरामीटर सूची की उम्मीद करता है । C सही होने के लिए, आपको अपना कोड बदलना चाहिए bool f1(void)
१०:२१ पर ulvitness

1
के main()लिए सरलीकरण किया जा सकता है int main() { puts(f1() == true ? "true" : "false"); puts(f1() ? "true" : "false"); return 0; }- यह विसंगति को बेहतर दिखाएगा।
पालेक

@uliwitness K & R 1st ed के बारे में। (1978) जब कोई नहीं था void?
1

@uliwitness वहाँ नहीं थे trueऔर falseकश्मीर एंड आर 1 एड में।, इसलिए नहीं सब पर इस तरह की समस्याओं थे। यह सच और झूठ के लिए सिर्फ 0 और गैर-शून्य था। है ना? मुझे नहीं पता कि उस समय प्रोटोटाइप उपलब्ध थे या नहीं।
Ho1

1
K & R 1st Edn ने पूर्ववर्ती प्रोटोटाइप्स (और C मानक) को एक दशक से अधिक (मानक के लिए पुस्तक बनाम 1989 के लिए) से पहले प्राप्त किया था - वास्तव में, C ++ और C कक्षा के साथ C भविष्य में तब भी था जब K & R1 प्रकाशित हुआ था। इसके अलावा, C99 से पहले, कोई _Boolप्रकार और कोई <stdbool.h>हेडर नहीं था ।
जोनाथन लेफ़लर

जवाबों:


396

जैसा कि अन्य उत्तरों में उल्लेख किया गया है, समस्या यह है कि आप gccकोई संकलक विकल्प सेट के साथ उपयोग करते हैं। यदि आप ऐसा करते हैं, तो यह "gnu90" कहलाता है, जो पुराने से गैर-मानक कार्यान्वयन है, 1990 से C90 मानक को वापस ले लेता है।

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

यह खतरनाक बकवास व्यवहार वर्ष 1999 में C99 मानक जारी करने के साथ तय किया गया था। समारोह की घोषणाओं पर रोक लगा दी गई थी।

दुर्भाग्य से, जीसीसी संस्करण 5.xx तक अभी भी डिफ़ॉल्ट रूप से पुराने सी मानक का उपयोग करता है। शायद कोई कारण नहीं है कि आपको अपना कोड कुछ भी लेकिन मानक सी के रूप में संकलित करना चाहिए। इसलिए आपको जीसीसी को स्पष्ट रूप से बताना होगा कि यह आपके कोड को आधुनिक सी कोड के रूप में संकलित करना चाहिए, बजाय कुछ 25+ वर्ष पुराने, गैर-मानक GNU बकवास ।

अपने प्रोग्राम को हमेशा इस प्रकार संकलित करके समस्या को ठीक करें:

gcc -std=c11 -pedantic-errors -Wall -Wextra
  • -std=c11 इसे वर्तमान (C) C मानक (अनौपचारिक रूप से C11 के रूप में जाना जाता है) के अनुसार संकलन करने का आधा-अधूरा प्रयास करने के लिए कहता है।
  • -pedantic-errors इसे पूरे दिल से कहता है कि उपरोक्त करें, और जब आप गलत कोड लिखते हैं जो सी मानक का उल्लंघन करता है, तो संकलक त्रुटियां दें।
  • -Wall इसका मतलब है कि मुझे कुछ अतिरिक्त चेतावनियाँ दें जो अच्छा हो।
  • -Wextra इसका मतलब है मुझे कुछ अन्य अतिरिक्त चेतावनियाँ दें जो आपके लिए अच्छी हो सकती हैं।

19
यह उत्तर समग्र रूप से सही है, लेकिन अधिक जटिल कार्यक्रमों के लिए किसी भी या सभी के कारण -std=gnu11अपेक्षा के अनुसार काम करने की अधिक संभावना है -std=c11: C11 (POSIX, X / Open, आदि) से परे लाइब्रेरी कार्यक्षमता की आवश्यकता है जो "ग्नू" में उपलब्ध है। विस्तारित मोड लेकिन सख्त अनुरूपता मोड में दबा दिया गया; सिस्टम हेडर में बग्स जो विस्तारित मोड में छिपे हुए हैं, उदाहरण के लिए गैर-टाइप टाइप की उपलब्धता; ट्रिग्राफ के अनजाने उपयोग (यह मानक मिसफिट "ग्नू" मोड में अक्षम है)।
zwol

5
इसी तरह के कारणों के लिए, जबकि मैं आम तौर पर उच्च चेतावनियों के उपयोग को प्रोत्साहित करता हूं, मैं चेतावनियों-त्रुटियों त्रुटियों के उपयोग का समर्थन नहीं कर सकता। -pedantic-errorsइससे कम तकलीफदेह नहीं है, -Werrorलेकिन दोनों वास्तविक प्रोग्राम की वास्तविक समस्या न होने पर भी मूल लेखक के परीक्षण में शामिल नहीं किए जाने वाले ऑपरेटिंग सिस्टम को संकलित करने में विफल हो सकते हैं।
zwol

7
@ लुंडिन इसके विपरीत, मैंने जो दूसरी समस्या का उल्लेख किया था (सिस्टम हेडर में बग्स जो कि सख्त अनुरूपता मोड से उजागर होते हैं) सर्वव्यापी है ; मैं व्यापक, व्यवस्थित परीक्षण किया है, और देखते हैं कोई व्यापक रूप से इस्तेमाल ऑपरेटिंग सिस्टम है कि कम से कम एक ऐसी बग नहीं है (दो साल पहले के रूप में वैसे भी,)। C प्रोग्राम जिन्हें केवल C11 की कार्यक्षमता की आवश्यकता होती है, बिना किसी अतिरिक्त परिवर्धन के, मेरे अनुभव में नियम के बजाय अपवाद भी हैं।
zwol

6
@joop यदि आप मानक C bool/ का उपयोग करते हैं, _Boolतो आप अपने C कोड को "C ++ - esque" तरीके से लिख सकते हैं, जहाँ आप मानते हैं कि सभी तुलनाएँ और तार्किक ऑपरेटर boolC ++ की तरह वापस लौटते हैं , भले ही वे intऐतिहासिक कारणों से C में वापसी करते हों । । इसका बहुत बड़ा फायदा है कि आप ऐसे सभी प्रकार के भावों की सुरक्षा के लिए स्थैतिक विश्लेषण टूल का उपयोग कर सकते हैं, और संकलन-समय पर सभी प्रकार के बगों को उजागर कर सकते हैं। यह स्व-दस्तावेजीकरण कोड के रूप में आशय व्यक्त करने का एक तरीका भी है। और कम महत्वपूर्ण बात, यह कुछ बाइट्स रैम को भी बचाता है।
लुंडिन

7
ध्यान दें कि C99 में अधिकांश नया सामान उस 25+ वर्ष पुराने GNU बकवास से आया है।
शाहबाज

141

आपके पास f1()main.c के लिए एक प्रोटोटाइप घोषित नहीं है , इसलिए इसे स्पष्ट रूप से परिभाषित किया गया है int f1(), जिसका अर्थ है कि यह एक फ़ंक्शन है जो अज्ञात संख्या में तर्क लेता है और रिटर्न करता है int

यदि intऔर boolविभिन्न आकार के हैं, तो इसका परिणाम अपरिभाषित व्यवहार में होगा । उदाहरण के लिए, मेरी मशीन पर, int4 बाइट्स हैं और boolएक बाइट है। चूंकि फ़ंक्शन को लौटने के लिए परिभाषित किया गया है bool, यह वापस आने पर स्टैक पर एक बाइट डालता है। हालाँकि, चूंकि यह मुख्य रूपint से main.c से लौटने की घोषणा है , इसलिए कॉलिंग फ़ंक्शन स्टैक से 4 बाइट्स पढ़ने की कोशिश करेगा।

Gcc में डिफ़ॉल्ट कंपाइलर विकल्प आपको यह नहीं बताएगा कि यह ऐसा कर रहा है। लेकिन अगर आप इसका संकलन करते हैं -Wall -Wextra, तो आपको यह मिलेगा:

main.c: In function ‘main’:
main.c:6: warning: implicit declaration of function ‘f1’

इसे ठीक करने के लिए f1, main.c के लिए पहले एक घोषणा जोड़ें main:

bool f1(void);

ध्यान दें कि तर्क सूची स्पष्ट रूप से सेट की गई है void, जो बताती है कि कंपाइलर फ़ंक्शन को कोई तर्क नहीं देता है, जैसा कि एक खाली पैरामीटर सूची के विपरीत है, जिसका अर्थ है अज्ञात संख्या में तर्क। f1इसे दर्शाने के लिए f1.c में परिभाषा भी बदलनी चाहिए।


2
कुछ मैं अपनी परियोजनाओं में करता था (जब मैंने अभी भी जीसीसी का उपयोग किया था) -Werror-implicit-function-declarationजीसीसी के विकल्पों में जोड़ा गया था , इस तरह यह अब अतीत से नहीं खिसकता है। एक बेहतर विकल्प यह भी है कि -Werrorसभी चेतावनियों को त्रुटियों में बदल दिया जाए। जब वे दिखाई दें तो आपको सभी चेतावनियों को ठीक करने के लिए मजबूर करता है।
१०:४१ पर ulvitness

2
आपको खाली कोष्ठक का उपयोग भी नहीं करना चाहिए क्योंकि ऐसा करना एक अश्लील विशेषता है। मतलब वे सी मानक के अगले संस्करण में इस तरह के कोड को प्रतिबंधित कर सकते हैं।
लुंडिन

1
@uliwitness आह। C ++ से आने वाले लोगों के लिए अच्छी जानकारी जो केवल C में
डब करते हैं

वापसी मूल्य आमतौर पर स्टैक में नहीं डाला जाता है, लेकिन एक रजिस्टर में। ओवेन का जवाब देखें। इसके अलावा, आप आमतौर पर एक बाइट को स्टैक में नहीं डालते हैं, लेकिन शब्द आकार का एक बहु।
22

जीसीसी के नए संस्करण (5.xx) अतिरिक्त झंडे के बिना उस चेतावनी को देते हैं।
ओवरव्यू

36

मुझे लगता है कि यह देखना दिलचस्प है कि लुंडिन के उत्कृष्ट उत्तर में उल्लेखित आकार-बेमेल वास्तव में कहां होता है।

यदि आप संकलन करते हैं --save-temps, तो आपको असेंबली फाइलें मिलेंगी जिन्हें आप देख सकते हैं। यहाँ वह भाग है जहाँ तुलना f1()करता है == 0और उसका मूल्य लौटाता है:

cmpl    $0, -4(%rbp)
sete    %al

लौटने वाला हिस्सा है sete %al। C के x86 कॉलिंग कन्वेंशन में, मान 4 बाइट्स या छोटे (जिसमें शामिल हैं intऔर bool) रजिस्टर के माध्यम से लौटाए जाते हैं %eax%alसबसे कम बाइट है %eax। तो, ऊपरी 3 बाइट्स को %eaxअनियंत्रित अवस्था में छोड़ दिया जाता है।

अब इसमें main():

call    f1
testl   %eax, %eax
je  .L2

चाहे यह जाँच करता है पूरी की %eax, क्योंकि यह सोचता है कि यह एक पूर्णांक का परीक्षण कर रहा है, शून्य है।

एक स्पष्ट कार्य घोषणा परिवर्तन जोड़ना main():

call    f1
testb   %al, %al
je  .L2

जो हम चाहते हैं।


27

कृपया इस तरह के एक आदेश के साथ संकलन करें:

gcc -Wall -Wextra -Werror -std=gnu99 -o main.exe main.c

आउटपुट:

main.c: In function 'main':
main.c:14:5: error: implicit declaration of function 'f1' [-Werror=impl
icit-function-declaration]
     printf( f1() == true ? "true\n" : "false\n");
     ^
cc1.exe: all warnings being treated as errors

ऐसे संदेश के साथ, आपको यह पता होना चाहिए कि इसे सही करने के लिए क्या करना चाहिए।

संपादित करें: (अब हटाए गए) टिप्पणी को पढ़ने के बाद, मैंने झंडे के बिना आपके कोड को संकलित करने का प्रयास किया। खैर, यह मुझे संकलक त्रुटियों को संकलक त्रुटियों के बजाय संकलक चेतावनी के साथ ले गया। और उन लिंकर त्रुटियों को समझना अधिक कठिन है, इसलिए यदि -std-gnu99आवश्यक नहीं है , तो भी कृपया कम से कम उपयोग करने की कोशिश करें, -Wall -Werrorइससे आपको गधे में बहुत दर्द से बचा जाएगा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.