एक स्टैनफोर्ड ट्यूटोरियल और जीसीसी के बीच संघर्ष


82

इस फिल्म के अनुसार (लगभग 38 मिनट), यदि मेरे पास एक ही स्थानीय संस्करण के साथ दो कार्य हैं, तो वे एक ही स्थान का उपयोग करेंगे। तो निम्नलिखित कार्यक्रम, मुद्रित करना चाहिए 5gccपरिणामों के साथ इसे संकलित करना -1218960859। क्यों?

कार्यक्रम:

#include <stdio.h>

void A()
{
    int a;
    printf("%i",a);
}

void B()
{
    int a;
    a = 5;
}

int main()
{
    B();
    A();
    return 0;
}

जैसा कि अनुरोध किया गया है, यहां डिस्सेम्बलर से आउटपुट है:

0804840c <A>:
 804840c:   55                      push   ebp
 804840d:   89 e5                   mov    ebp,esp
 804840f:   83 ec 28                sub    esp,0x28
 8048412:   8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
 8048415:   89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 8048419:   c7 04 24 e8 84 04 08    mov    DWORD PTR [esp],0x80484e8
 8048420:   e8 cb fe ff ff          call   80482f0 <printf@plt>
 8048425:   c9                      leave  
 8048426:   c3                      ret    

08048427 <B>:
 8048427:   55                      push   ebp
 8048428:   89 e5                   mov    ebp,esp
 804842a:   83 ec 10                sub    esp,0x10
 804842d:   c7 45 fc 05 00 00 00    mov    DWORD PTR [ebp-0x4],0x5
 8048434:   c9                      leave  
 8048435:   c3                      ret    

08048436 <main>:
 8048436:   55                      push   ebp
 8048437:   89 e5                   mov    ebp,esp
 8048439:   83 e4 f0                and    esp,0xfffffff0
 804843c:   e8 e6 ff ff ff          call   8048427 <B>
 8048441:   e8 c6 ff ff ff          call   804840c <A>
 8048446:   b8 00 00 00 00          mov    eax,0x0
 804844b:   c9                      leave  
 804844c:   c3                      ret    
 804844d:   66 90                   xchg   ax,ax
 804844f:   90                      nop

41
"वे अच्छी तरह से एक ही स्थान का उपयोग करते हैं" - यह गलत है। वो शायद। या वे नहीं कर सकते हैं। और आप इस तरह से भरोसा नहीं कर सकते।
Mat

17
मुझे आश्चर्य है कि एक अभ्यास के रूप में इसका क्या उपयोग होता है, अगर कोई उत्पादन कोड में इसका उपयोग करेगा तो एक को गोली मार दी जाएगी।
एंडर्सके

12
@claptrap यह जानने के लिए कि कॉल स्टैक कैसे काम करता है और यह समझ सकता है कि कंप्यूटर हुड के नीचे क्या कर रहा है? लोग इस तरीके को बहुत गंभीरता से ले रहे हैं।
जोनाथन रेनहार्ट

9
@claptrap फिर, यह एक सीखने का अभ्यास है । "हुप्स आपको कूदना पड़ता है" सभी समझ में आता है अगर आप समझते हैं कि विधानसभा स्तर पर क्या हो रहा है। मुझे गंभीरता से संदेह है कि ओपी के पास "वास्तविक" कार्यक्रम में कुछ इस तरह का उपयोग करने का कोई इरादा है (यदि वह करता है, तो उसे लात मार दी जानी चाहिए!)
जोनाथन रेइनहर्ट

12
उदाहरण बिना सोचे-समझे भ्रामक है, क्योंकि दो स्थानीय चर का एक ही नाम है; लेकिन यह क्या चल रहा है इसके लिए अप्रासंगिक है: केवल चर और संख्याएं ही मायने रखती हैं। अलग-अलग नामों को समान रूप से काम करना चाहिए।
एलेक्सिस

जवाबों:


130

हाँ, हाँ, यह है अपरिभाषित व्यवहार है, क्योंकि आप चर अप्रारंभीकृत उपयोग कर रहे हैं 1

हालांकि, x86 आर्किटेक्चर 2 पर , यह प्रयोग काम करना चाहिए । स्टैक से मान "मिटा" नहीं है, और चूंकि यह आरंभिक नहीं है B(), इसलिए समान मूल्य अभी भी होना चाहिए, बशर्ते स्टैक फ़्रेम समान हो।

मैं यह अनुमान लगाने के लिए उद्यम करूंगा कि चूंकि अंदर का उपयोगint a नहीं किया गया है void B(), कंपाइलर ने उस कोड को ऑप्टिमाइज़ किया, और स्टैक पर उस स्थान पर 5 कभी नहीं लिखा गया था। एक जोड़ने का प्रयास करें printfमें B()यह सिर्फ काम कर सकते हैं - और साथ।

इसके अलावा, संकलक झंडे - अर्थात् अनुकूलन स्तर - संभवतः इस प्रयोग को भी प्रभावित करेगा। -O0Gcc पास करके अनुकूलन को अक्षम करने का प्रयास करें ।

संपादित करें: मैंने आपके कोड को केवल gcc -O0(64-बिट) के साथ संकलित किया है , और वास्तव में, प्रोग्राम 5 प्रिंट करता है, जैसा कि कॉल स्टैक से परिचित व्यक्ति अपेक्षा करेगा। वास्तव में, इसके बिना भी काम किया -O0। 32-बिट बिल्ड अलग व्यवहार कर सकता है।

अस्वीकरण: कभी मत करो, कभी भी "वास्तविक" कोड में कुछ इस तरह का उपयोग करें!

1 - इस बारे में नीचे बहस चल रही है कि यह आधिकारिक तौर पर "यूबी" है, या अप्रत्याशित है।

2 - इसके अलावा x64, और शायद हर दूसरी वास्तुकला जो कॉल स्टैक का उपयोग करती है (कम से कम एमएमयू के साथ)


आइए एक कारण देखें कि यह काम क्यों नहीं करता है। यह सबसे अच्छा 32 बिट में देखा जाता है, इसलिए मैं इसके साथ संकलन करूंगा -m32

$ gcc --version
gcc (GCC) 4.7.2 20120921 (Red Hat 4.7.2-2)

मैंने $ gcc -m32 -O0 test.c(अनुकूलन अक्षम) के साथ संकलित किया । जब मैं इसे चलाता हूं, तो यह कचरा प्रिंट करता है।

देख रहे हैं $ objdump -Mintel -d ./a.out:

080483ec <A>:
 80483ec:   55                      push   ebp
 80483ed:   89 e5                   mov    ebp,esp
 80483ef:   83 ec 28                sub    esp,0x28
 80483f2:   8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
 80483f5:   89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 80483f9:   c7 04 24 c4 84 04 08    mov    DWORD PTR [esp],0x80484c4
 8048400:   e8 cb fe ff ff          call   80482d0 <printf@plt>
 8048405:   c9                      leave  
 8048406:   c3                      ret    

08048407 <B>:
 8048407:   55                      push   ebp
 8048408:   89 e5                   mov    ebp,esp
 804840a:   83 ec 10                sub    esp,0x10
 804840d:   c7 45 fc 05 00 00 00    mov    DWORD PTR [ebp-0x4],0x5
 8048414:   c9                      leave  
 8048415:   c3                      ret    

हम देखते हैं कि B, संकलक 0x10 बाइट्स स्टैक स्पेस को आरक्षित करता है, और 5 int aपर हमारे चर को इनिशियलाइज़ करता है [ebp-0x4]

में Aहालांकि, संकलक रखा int aपर [ebp-0xc]। तो इस मामले में हमारे स्थानीय चर एक ही स्थान पर समाप्त नहीं हुए! के रूप में अच्छी तरह से printf()कॉल करने के Aलिए Aऔर Bसमान होने के लिए स्टैक फ्रेम का कारण होगा , और प्रिंट 55


7
अच्छा अस्वीकरण!
टोबियास वेरेत्र

5
यहां तक ​​कि अगर यह एक बार काम करता है, तो यह कुछ आर्किटेक्चर पर विश्वसनीय नहीं होगा - एक रुकावट किसी भी समय स्टैक पॉइंटर के नीचे मेरा सब कुछ उड़ा देती है।
मार्टिन जेम्स

6
एक जवाब के लिए बहुत सारे वोट जो "अपरिभाषित व्यवहार" का भी उल्लेख नहीं करता है। इसके शीर्ष पर, यह भी स्वीकार किया जाता है।
B22овиЈ

25
इसके अलावा, यह स्वीकार किया जाता है क्योंकि यह वास्तव में सवाल का जवाब देता है
स्लीपबेटमैन

8
@ B @овиЈ क्या आपने कोई वीडियो देखा? देखिए, हर कोई और उनका भाई जानता है कि आपको वास्तविक कोड में ऐसा नहीं करना चाहिए, और यह अपरिभाषित व्यवहार को आमंत्रित करता है । ये मुद्दा नहीं है। मुद्दा यह है कि एक कंप्यूटर एक अच्छी तरह से परिभाषित, पूर्वानुमानित मशीन है। एक x86 बॉक्स (और शायद सबसे अन्य आर्किटेक्चर) पर, एक संकलक और संभावित रूप से कुछ कोड / ध्वज मालिश के साथ, यह अपेक्षा के अनुरूप काम करेगा । यह कोड, वीडियो के साथ-साथ कॉल स्टैक कैसे काम करता है , इसका एक प्रदर्शन है। यदि यह आपको बुरी तरह परेशान करता है, तो मेरा सुझाव है कि आप कहीं और जाएं। हममें से कुछ जिज्ञासु प्रकार चीजों को समझना पसंद करते हैं।
जोनाथन रेनहार्ट

36

यह अपरिभाषित व्यवहार है । एक असंक्रमित स्थानीय चर का अनिश्चित मान होता है, और इसका उपयोग करने से अपरिभाषित व्यवहार होता है।


6
अधिक सटीक होने के लिए, एक इकाईगत चर का उपयोग करना, जिसमें पता कभी नहीं लिया जाता है, अपरिभाषित व्यवहार है।
जेन्स गुस्तेद

@JensGustedt अच्छी टिप्पणी। क्या आपके पास ब्लॉग के "अगले उदाहरण" अनुभाग के बारे में कुछ भी कहना है ।frama-c.com/index.php ? post/2013/03/13/… ?
पास्कल क्यूक

@ PascalCuoq, यह भी मानक समिति में एक चल रही चर्चा लगती है। ऐसी स्थितियां हैं जो एक संकेतक के माध्यम से मिलने वाली स्मृति का निरीक्षण करती हैं, इससे समझ में आता है, भले ही आपको पता न हो कि यह आरंभिक है या नहीं। बस इसे सभी मामलों में अपरिभाषित बनाना बहुत अधिक प्रतिबंधात्मक है।
जेन्स गुस्टेड

@JensGustedt: परिभाषित व्यवहार करने के लिए इसका उपयोग करने के कारण इसका पता कैसे चलता है: { int uninit; &uninit; printf("%d\n", uninit); }अभी भी अपरिभाषित व्यवहार है। दूसरी ओर, आप किसी भी वस्तु को एक सरणी के रूप में मान सकते हैं unsigned char; क्या आपके मन में था?
कीथ थॉम्पसन

@KeithThompson, नहीं, यह दूसरा तरीका है। वैरिएबल का ऐसा होना कि इसका पता कभी न लगे और इसे आरंभीकृत न किया जाए। अपने आप से अनिश्चित मान पढ़ना अपरिभाषित व्यवहार नहीं है, सामग्री सिर्फ अप्रत्याशित है। 6.3.2.1 P2 से: यदि
जेन्स गुस्टेड

12

एक महत्वपूर्ण बात को याद है - नहीं है कभी ऐसा ही कुछ पर भरोसा करते हैं और कभी नहीं वास्तविक कोड में इसके उपयोग! यह सिर्फ एक दिलचस्प बात है (जो हमेशा सच भी नहीं है), एक विशेषता या ऐसा कुछ नहीं। कल्पना कीजिए कि "फीचर" - दुःस्वप्न द्वारा उत्पादित बग को खोजने की कोशिश करें।

Btw। - C और C ++ उस तरह के "फीचर्स" से भरे हुए हैं, यहाँ इसके बारे में ग्रेट स्लाइडशो है: http://www.slideshare.net/olvemaudal/deep-c इसलिए यदि आप अधिक समान "फीचर्स" देखना चाहते हैं, तो समझें कि क्या है हुड के नीचे और यह कैसे काम कर रहा है बस इस स्लाइड शो को देखें - आपको अफसोस नहीं होगा और मुझे यकीन है कि अधिकांश अनुभवी c / c ++ प्रोग्रामर भी इससे बहुत कुछ सीख सकते हैं।


7

फ़ंक्शन में A, चर aको प्रारंभ नहीं किया जाता है, इसके मूल्य को प्रिंट करने से अपरिभाषित व्यवहार होता है।

कुछ संकलक में, चर aमें Aऔर aमें Bतो यह प्रिंट कर सकते हैं, एक ही पते में हैं 5, लेकिन फिर, आप अपरिभाषित व्यवहार पर भरोसा नहीं कर सकते।


1
ट्यूटोरियल 100% सही है, लेकिन क्या मूल पोस्टर s machine will be the same depends on the assembly generated by the compiler. As @JonathonReinhart pointed out the call to B () ` पर परिणाम दूर अनुकूलित किए जा सकते हैं।
लॉयड क्रॉले

1
मुझे "उस ट्यूटोरियल गलत है" वर्बेज के साथ एक समस्या है। क्या आप वास्तव में ट्यूटोरियल देखने गए थे? यह आपको सिखाने की कोशिश नहीं कर रहा है कि इस तरह से पागल कैसे किया जाए, लेकिन यह दिखाने के लिए कि कॉल स्टैक कैसे काम करता है। उस स्थिति में, ट्यूटोरियल पूरी तरह से सही है।
जोनाथन रेनहार्ट

@JonathonReinhart मैंने ट्यूटोरियल नहीं देखा, सोचा कि यह उदाहरण ट्यूटोरियल से था, मैं इस हिस्से को हटा दूँगा।
यू हाओ

@ लॉयडक्रॉली मैंने ट्यूटोरियल के बारे में भाग हटा दिया है। मुझे पता है कि यह स्टैक आर्किटेक्चर के बारे में है, यही मेरा मतलब है कि वे एक ही पते पर हैं जब यह प्रिंट किया था 5, लेकिन जाहिर तौर पर जोनाथन रेनहार्ट की बहुत बेहतर व्याख्या है।
यू हाओ

7

अपना कोड संकलित करें gcc -Wall filename.cआप इन चेतावनियों को देखेंगे।

In function 'B':
11:9: warning: variable 'a' set but not used [-Wunused-but-set-variable]

In function 'A':
6:11: warning: 'a' is used uninitialized in this function [-Wuninitialized]  

C में अनइंस्टाल्ड वैरिएबल को प्रिंट करते हुए अपरिभाषित व्यवहार की ओर ले जाता है।

धारा 6.7.8 C99 मानक का प्रारंभ कहता है

यदि कोई ऐसी वस्तु जिसमें स्वचालित भंडारण अवधि है, को स्पष्ट रूप से प्रारंभ नहीं किया गया है, तो इसका मूल्य अनिश्चित है। यदि स्थिर भंडारण अवधि वाली कोई वस्तु स्पष्ट रूप से आरंभ नहीं की जाती है, तो:

if it has pointer type, it is initialized to a null pointer;
— if it has arithmetic type, it is initialized to (positive or unsigned) zero;
— if it is an aggregate, every member is initialized (recursively) according to these rules;
— if it is a union, the first named member is initialized (recursively) according to these rules.

Edit1

@Jonathon Reinhart के रूप में यदि आप -Oध्वज का उपयोग करके अनुकूलन अक्षम करते हैं gcc-O0 तो आपको आउटपुट 5 प्राप्त हो सकता है।

लेकिन यह बिल्कुल अच्छा विचार नहीं है, कभी भी उत्पादन कोड में इसका उपयोग न करें।

-Wuninitialized यह मूल्यवान चेतावनी में से एक है। आपको इस पर विचार करना चाहिए। आपको या तो इस चेतावनी को निष्क्रिय नहीं करना चाहिए या इस चेतावनी को छोड़ देना चाहिए जो डेमों को चलाने के दौरान क्रैश का कारण बनता है।


EDIT2

डीप सी स्लाइड्स में बताया गया है कि परिणाम 5 / कचरा क्यों है। इस उत्तर को थोड़ा और प्रभावी बनाने के लिए मामूली संशोधनों के साथ उन स्लाइड्स से इस जानकारी को जोड़ना।

केस 1: अनुकूलन के बिना

$ gcc -O0 file.c && ./a.out  
5

शायद इस कंपाइलर में नामित चर का एक पूल है जिसे वह पुन: उपयोग करता है। जैसे चर का उपयोग किया गया था और में जारी किया गया था B(), तब जब A()पूर्णांक नामों की आवश्यकता aहोगी तो चर को वही मेमोरी स्थान मिलेगा। यदि आप में चर का नाम बदलते हैं B(), कहते हैं b, तो मुझे नहीं लगता कि आपको मिलेगा 5

केस 2: अनुकूलन के साथ

जब ऑप्टिमाइज़र किक करता है तो बहुत सी चीजें हो सकती हैं। इस मामले में मुझे लगता है कि कॉल को B()छोड़ दिया जा सकता है क्योंकि इसका कोई साइड इफेक्ट नहीं है। इसके अलावा, अगर कोई A()इनकमिंग main()कॉल नहीं है, तो मुझे आश्चर्य नहीं होगा । (लेकिन जब A ()से लिंकर दृश्यता समारोह के लिए ऑब्जेक्ट कोड अभी भी बनाया जाना चाहिए मामले में एक और वस्तु फ़ाइल समारोह के साथ लिंक करना चाहता है)। वैसे भी, मुझे संदेह है कि मुद्रित मूल्य कुछ और होगा यदि आप कोड का अनुकूलन करते हैं।

gcc -O file.c && ./a.out
1606415608  

कूड़ा!


1
Edit 2, Case 1 में आपका तर्क पूरी तरह से गलत है। यही कारण है कि नहीं है सब पर है कि यह कैसे काम करता है। स्थानीय चर के नाम का अर्थ है कुछ भी नहीं।
जोनाथन रेनहार्ट

@JonathonReinhart जैसा कि मैंने उत्तर में बताया है कि मैंने इसे डीप स्लाइड से जोड़ा है, कृपया बताएं कि यह किस आधार पर गलत है।
गंगाधर

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

@DanHaynes आपकी टिप्पणी मुझे आश्वस्त करती है। दूसरे फंक्शन कॉल में बैकस्ट फ्रेम फर्स्ट फंक्शन कॉल के स्टैक फ्रेम को ओवरले कर सकता है जहाँ तक वेरिएबल टाइप और फंक्शन प्रोटोटाइप समान है। हाँ मैं भी मानता हूँ कि वैरिएबल नामों से कोई लेना देना नहीं है।
गंगाधर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.