2013-07-23 8 views
14

मैं this code from here (चीनी में) पढ़ रहा हूं। सी में ग्लोबल वैरिएबल का परीक्षण करने के बारे में कोड का एक टुकड़ा है। परिवर्तनीय a फ़ाइल t.h में परिभाषित किया गया है जिसे दो बार शामिल किया गया है। फ़ाइल foo.c में struct b को कुछ मान और main फ़ंक्शन के साथ परिभाषित किया गया है। main.c फ़ाइल में, प्रारंभ किए बिना दो चर परिभाषित किया गया।सी एक ही वैश्विक चर विभिन्न फाइलों में परिभाषित

/* t.h */ 
#ifndef _H_ 
#define _H_ 
int a; 
#endif 

/* foo.c */ 
#include <stdio.h> 
#include "t.h" 

struct { 
    char a; 
    int b; 
} b = { 2, 4 }; 

int main(); 

void foo() 
{ 
    printf("foo:\t(&a)=0x%08x\n\t(&b)=0x%08x\n 
     \tsizeof(b)=%d\n\tb.a=%d\n\tb.b=%d\n\tmain:0x%08x\n", 
     &a, &b, sizeof b, b.a, b.b, main); 
} 

/* main.c */ 
#include <stdio.h> 
#include "t.h" 

int b; 
int c; 

int main() 
{ 
    foo(); 
    printf("main:\t(&a)=0x%08x\n\t(&b)=0x%08x\n 
     \t(&c)=0x%08x\n\tsize(b)=%d\n\tb=%d\n\tc=%d\n", 
     &a, &b, &c, sizeof b, b, c); 
    return 0; 
} 

उबंटू जीसीसी 4.4.3 संकलन का उपयोग कर के बाद, परिणाम यह नीचे की तरह है:

foo: (&a)=0x0804a024 
    (&b)=0x0804a014 
    sizeof(b)=8 
    b.a=2 
    b.b=4 
    main:0x080483e4 
main: (&a)=0x0804a024 
    (&b)=0x0804a014 
    (&c)=0x0804a028 
    size(b)=4 
    b=2 
    c=0 

चर a और b दो समारोह में एक ही पते है, लेकिन b के आकार बदल गया है। मैं समझ नहीं पा रहा हूं कि यह कैसे काम करता है!

+3

आपका प्रश्न क्या है? –

+2

मुद्रण पॉइंटर्स के लिए '% p' का उपयोग करें, आपको अपने शीर्षलेख में 'foo' जोड़ना चाहिए। – Nobilis

+0

यदि आप संघर्ष को हल करना चाहते हैं, तो विभिन्न स्थिर घोषित करें। – snf

उत्तर

19

आप सी "एक परिभाषा शासन" का उल्लंघन कर रहे है, और परिणाम अपरिभाषित व्यवहार है।मानक में "एक परिभाषा नियम" औपचारिक रूप से नहीं बताया गया है। हम विभिन्न स्रोत फ़ाइलों (उर्फ, अनुवाद इकाइयों) में वस्तुओं को देख रहे हैं, इसलिए हम "बाहरी परिभाषाओं" से चिंतित हैं। "एक बाहरी परिभाषा" अर्थ (सी 11 6.9 पी 5) से बताया जाता है:

एक बाहरी परिभाषा एक बाहरी घोषणा भी एक समारोह (एक इनलाइन परिभाषा के अलावा अन्य) या किसी वस्तु की एक परिभाषा है। यदि बाहरी लिंकेज के साथ घोषित एक पहचानकर्ता को अभिव्यक्ति में उपयोग किया जाता है (sizeof या _Alignof ऑपरेटर के ऑपरेशन के हिस्से के अलावा जिसका परिणाम एक पूर्णांक स्थिरांक है), पूरे कार्यक्रम में कहीं पहचानकर्ता के लिए बिल्कुल एक बाहरी परिभाषा होगी; अन्यथा, एक से अधिक नहीं होगा।

कौन सा मूल रूप से मतलब है कि आप केवल करने की अनुमति है सबसे एक बार पर एक वस्तु को परिभाषित। (अन्यथा खंड आप बिल्कुल एक बाहरी वस्तु को परिभाषित नहीं करने के लिए अगर यह कार्यक्रम में कहीं भी इस्तेमाल कभी नहीं किया है की अनुमति देता है।)

नोट आप b के लिए दो बाहरी परिभाषाओं है। एक संरचना है कि आप foo.c में प्रारंभ है, और अन्य main.c में जांच परिभाषा, (सी 11 6.9.2 p1-2) है:

तो एक वस्तु के लिए एक पहचानकर्ता की घोषणा फ़ाइल गुंजाइश है और एक प्रारंभकर्ता, घोषणा पहचानकर्ता के लिए बाहरी परिभाषा है।

एक वस्तु एक प्रारंभकर्ता बिना गुंजाइश फ़ाइल है कि के लिए एक पहचानकर्ता के एक घोषणा, और एक भंडारण-वर्ग विनिर्देशन के बिना या भंडारण-वर्ग विनिर्देशक static के साथ, एक जांच परिभाषा का गठन किया। यदि किसी अनुवाद इकाई में पहचानकर्ता के लिए एक या अधिक टेटेटिव परिभाषाएं होती हैं, और अनुवाद इकाई में उस पहचानकर्ता के लिए कोई बाहरी परिभाषा नहीं होती है, तो व्यवहार बिल्कुल वैसा ही होता है जैसे अनुवाद इकाई में उस पहचानकर्ता की फ़ाइल स्कोप घोषणा होती है, समग्र प्रकार के साथ अनुवाद इकाई के अंत में, 0.

के बराबर प्रारंभिक के साथ तो आपके पास b की कई परिभाषाएं हैं। हालांकि, एक और त्रुटि है, जिसमें आपने b को विभिन्न प्रकार के साथ परिभाषित किया है। सबसे पहले ध्यान दें कि बाहरी लिंक के साथ एक ही ऑब्जेक्ट में कई घोषणाओं की अनुमति है।

अनुवाद इकाइयों और पुस्तकालयों के सेट है कि एक पूरे कार्यक्रम का गठन किया, प्रत्येक: हालांकि, जब एक ही नाम दो अलग-अलग स्रोत फ़ाइलों में प्रयोग किया जाता है, उस नाम एक ही वस्तु (सी 11 6.2.2 p2) को संदर्भित करता है बाहरी लिंकेज के साथ एक विशेष पहचानकर्ता की घोषणा एक ही ऑब्जेक्ट या फ़ंक्शन को दर्शाती है।

सी एक ही वस्तु के लिए घोषणाओं पर एक सख्त सीमा डालता है (C11 6.2.7 p2):

सभी घोषणाओं है कि एक ही वस्तु या समारोह का उल्लेख संगत प्रकार जाएगा; अन्यथा, व्यवहार अपरिभाषित है।

चूंकि आपकी प्रत्येक स्रोत फ़ाइलों में b के प्रकार वास्तव में मेल नहीं खाते हैं, व्यवहार अपरिभाषित है। (क्या एक संगत प्रकार C11 6.2.7 के सभी में विस्तार से वर्णन किया गया है का गठन किया है, लेकिन यह मूल रूप से किया जा रहा है कि प्रकार से मेल करने के लिए निर्भर करता है।)

तो तुम b के लिए दो असफलताओं है:

  • एकाधिक परिभाषाएं।
  • असंगत प्रकारों के साथ एकाधिक घोषणाएं।

तकनीकी रूप से, आपकी दोनों स्रोत फ़ाइलों में int a की आपकी घोषणा "एक परिभाषा नियम" का भी उल्लंघन करती है। नोट a बाहरी लिंकेज (C11 6.2.2 पी 5) है:

एक वस्तु के लिए एक पहचानकर्ता की घोषणा गुंजाइश और बिना किसी संग्रहण-वर्ग विनिर्देशन फ़ाइल नहीं है तो उसके संबंध बाहरी है।

लेकिन, C11 6.9.2 पहले से बोली से, उन int a जांच परिभाषाओं बाहरी परिभाषाएं दी गई हैं, और आप केवल शीर्ष पर सी 11 6.9 से बोली से उन में से एक की अनुमति है।

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


सेल्सियस के लिए एक आम विस्तार कई बाहरी परिभाषाओं अनुमति देने के लिए है, और सूचनात्मक अनुलग्नक J.5 (सी 11 J.5.11) में सी मानक में वर्णित है:

वहाँ अधिक हो सकता है कीवर्ड extern के स्पष्ट उपयोग के बिना किसी ऑब्जेक्ट के पहचानकर्ता के लिए एक बाहरी परिभाषा से, या ; यदि परिभाषाएं से असहमत हैं, या एक से अधिक प्रारंभिक हैं, व्यवहार (6.9.2) को अपरिभाषित है।

(जोर मेरा है।) के बाद से a की परिभाषा दी गई सहमति व्यक्त करते हैं, वहाँ कोई नुकसान नहीं है, लेकिन b के लिए परिभाषाएँ सहमत नहीं हूं। यह एक्सटेंशन बताता है कि आपका कंपाइलर एकाधिक परिभाषाओं की उपस्थिति के बारे में शिकायत क्यों नहीं करता है। सी 11 6.2.2 के उद्धरण से, लिंकर एक ही ऑब्जेक्ट के एकाधिक संदर्भों को मेल करने का प्रयास करेगा।

Linkers आमतौर पर एकाधिक अनुवाद इकाइयों में एक ही प्रतीक की कई परिभाषाएं मिलान के लिए दो मॉडलों में से एक का उपयोग करें। ये "सामान्य मॉडल" और "रेफरी/डीफ़ मॉडल" हैं। "सामान्य मॉडल" में, एक ही नाम वाले एकाधिक ऑब्जेक्ट्स को एक ही ऑब्जेक्ट में union शैली तरीके से तब्दील किया जाता है ताकि ऑब्जेक्ट सबसे बड़ी परिभाषा के आकार पर हो। "रेफरी/डीफ़ मॉडल" में, प्रत्येक बाहरी नाम में बिल्कुल एक परिभाषा होनी चाहिए।

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

-fno-common विकल्प का उपयोग कर जीएनयू कंपाइलर में "सामान्य मॉडल" को दबाया जा सकता है।

$ cat a.c 
#include <stdio.h> 
int a; 
struct { char a; int b; } b = { 2, 4 }; 
void foo() { printf("%zu\n", sizeof(b)); } 
$ cat b.c 
#include <stdio.h> 
extern void foo(); 
int a, b; 
int main() { printf("%zu\n", sizeof(b)); foo(); } 
$ gcc -fno-common a.c b.c 
/tmp/ccd4fSOL.o:(.bss+0x0): multiple definition of `a' 
/tmp/ccMoQ72v.o:(.bss+0x0): first defined here 
/tmp/ccd4fSOL.o:(.bss+0x4): multiple definition of `b' 
/tmp/ccMoQ72v.o:(.data+0x0): first defined here 
/usr/bin/ld: Warning: size of symbol `b' changed from 8 in /tmp/ccMoQ72v.o to 4 in /tmp/ccd4fSOL.o 
collect2: ld returned 1 exit status 
$ 

मैं व्यक्तिगत रूप से लिंकर द्वारा जारी किए गए पिछले चेतावनी लग रहा है हमेशा संकल्प की परवाह किए बिना प्रदान की जानी चाहिए: जब मैं अपने सिस्टम पर इस परीक्षण किया है, यह आपसे मिलते-जुलते कोड के लिए कारण होता है "सख्त रेफरी/डेफ मॉडल" व्यवहार एकाधिक ऑब्जेक्ट परिभाषाओं के लिए मॉडल, लेकिन यह न तो यहां और न ही वहां है।


संदर्भ:
Unfortunately, I can't give you the link to my copy of the C11 Standard
What are extern variables in C?
The "Beginner's Guide to Linkers"
SAS Documentation on External Variable Models

+1

यह ध्यान देने योग्य है कि दो सामान्य कार्यान्वयन जिन्हें आमतौर पर "डीफ़/रेफ मॉडल" और "सामान्य मॉडल" कहा जाता है। उत्तरार्द्ध सामान्य यूनिक्स/लिनक्स लिंकर्स का उपयोग करता है और मनाए गए व्यवहार को जन्म देता है। "आम मॉडल" वाक्यांश 1 9 60 के दशक में कई मेनफ्रेम पर लागू किए गए फोरट्रान कॉमॉन ब्लॉक को संदर्भित करता है। – torek

+0

@torek: मुझे थोड़ा और अनुसंधान करने के लिए प्रेरित करने के लिए धन्यवाद। मैंने जवाब अपडेट कर लिया है। – jxh

+0

शायद बेवकूफ़ है, लेकिन एक अनुवाद इकाई वास्तव में क्या है? – NickHalden

0

समय foo पर संकलित किया जा रहा है, b दायरे में है कि दो ints वेक्टर {2, 4} या 8 बाइट्स एक sizeof (int) 4. जब मुख्य संकलित किया गया है, ख सिर्फ एक int रूप redeclared किया गया है जब तो 4 का आकार समझ में आता है। इसके अलावा "ए" के बाद संरचना में "पैडिंग बाइट्स" जोड़ा गया है, जैसे कि अगले स्लॉट (int) को 4 बाइट सीमा पर गठबंधन किया गया है।

-1

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

1

b क्योंकि लिंकर आप के लिए संघर्ष का समाधान करने का फैसला किया ही पता होता है।

sizeof क्योंकि sizeof संकलन समय पर मूल्यांकन किया जाता है विभिन्न मूल्यों को दर्शाता है। इस स्तर पर, कंपाइलर केवल एक b (वर्तमान फ़ाइल में परिभाषित एक) के बारे में जानता है।

+1

+1 यह जानने के लिए +1 था कि 'बी' को उसी स्मृति स्थान में रखा गया था। एक परिभाषा नियम उल्लंघन से बचने के लिए – jxh

2

कोड का टुकड़ा उद्देश्य पर एक परिभाषा नियम तोड़ने लगता है। यह अपरिभाषित व्यवहार का आह्वान करेगा, ऐसा मत करो।

वैश्विक चर के बारे में a: एक शीर्षलेख फ़ाइल में वैश्विक चर की परिभाषा न डालें, क्योंकि इसे एकाधिक .c फ़ाइलों में शामिल किया जाएगा, और कई परिभाषाओं की ओर जाता है। बस शीर्षलेख में घोषणाएं डालें और परिभाषा को .c फ़ाइलों में से एक में रखें।

t.h में:

extern int a; 

foo.c

int a; 

वैश्विक चर b बारे में: इसे कई बार परिभाषित नहीं करते, static का प्रयोग कर एक फाइल में चर सीमित करने के लिए।

foo.c में:

static struct { 
    char a; 
    int b; 
} b = { 2, 4 }; 

main.c

static int b; 
+0

विशिष्ट उपायों के लिए +1। – jxh

3

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

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

आपका केस सामान्य एक्सटेंशन विवरण से मेल नहीं खाता है। आपका कोड उस सामान्य एक्सटेंशन के साइड इफेक्ट के रूप में संकलित करता है, लेकिन इसका व्यवहार अभी भी अनिर्धारित है।

+0

मैंने पहले ही इस जवाब को पहले ही वोट दिया था, लेकिन इस जवाब ने पहले विशेष रूप से सामान्य एक्सटेंशन को संबोधित किया जो कई बाहरी परिभाषाओं के लिए अनुमति देता था। – jxh

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