हमें C ++ में "C" {#include <foo.h>} बाहरी की आवश्यकता क्यों है?


136

हमें उपयोग करने की आवश्यकता क्यों है:

extern "C" {
#include <foo.h>
}

विशेष रूप से:

  • हमें इसका उपयोग कब करना चाहिए?

  • संकलक / लिंकर स्तर पर क्या हो रहा है जो हमें इसका उपयोग करने की आवश्यकता है?

  • संकलन / लिंकिंग के संदर्भ में यह उन समस्याओं को कैसे हल करता है जिनके लिए हमें इसका उपयोग करने की आवश्यकता है?

जवाबों:


122

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

(वास्तव में नॉटी-ग्रिट्टी में जाने के लिए, C ++ के ABI आम तौर पर अपने फ़ंक्शन / विधियों के नाम 'मंगल' करते हैं, इसलिए printf()प्रोटोटाइप को C फ़ंक्शन के रूप में फ़्लैग किए बिना कॉल किया जाता है, C ++ वास्तव में कोड कॉलिंग उत्पन्न करेगा _Zprintf, साथ ही साथ अंत में अतिरिक्त स्क्रैप भी करेगा। )

तो: extern "C" {...}एसी हैडर सहित जब उपयोग करें — यह इतना आसान है। अन्यथा, आपके पास संकलित कोड में एक बेमेल होगा, और लिंकर चोक हो जाएगा। अधिकांश हेडर के लिए, हालाँकि, आपको इसकी आवश्यकता भी नहीं होगी externक्योंकि अधिकांश सिस्टम C हेडर पहले से ही इस तथ्य के लिए जिम्मेदार होंगे कि उन्हें C ++ कोड द्वारा शामिल किया जा सकता है और पहले से ही externउनका कोड।


1
क्या आप अधिक विस्तार से बता सकते हैं "अधिकांश सिस्टम C हेडर पहले से ही इस तथ्य के लिए जिम्मेदार होंगे कि वे C ++ कोड द्वारा शामिल किए जा सकते हैं और पहले से ही अपने कोड को बाहर कर सकते हैं।" ?
एम।

7
@BulatM। वे कुछ इस तरह से होते हैं: #ifdef __cplusplus extern "C" { #endif इसलिए जब C ++ फ़ाइल से शामिल किया जाता है तो उन्हें अभी भी C हैडर के रूप में माना जाता है।
कालमेरी

111

बाहरी "C" निर्धारित करता है कि उत्पन्न ऑब्जेक्ट फ़ाइल में प्रतीकों का नाम कैसे दिया जाना चाहिए। यदि कोई फ़ंक्शन एक्सटर्नल "C" के बिना घोषित किया जाता है, तो ऑब्जेक्ट फ़ाइल में प्रतीक का नाम C ++ नाम मैनलिंग का उपयोग करेगा। यहाँ एक उदाहरण है।

दिए गए परीक्षण की तरह। सी:

void foo() { }

ऑब्जेक्ट फ़ाइल में प्रतीकों को संकलित करना और सूचीबद्ध करना:

$ g++ -c test.C
$ nm test.o
0000000000000000 T _Z3foov
                 U __gxx_personality_v0

फू फ़ंक्शन को वास्तव में "_Z3foov" कहा जाता है। इस स्ट्रिंग में अन्य प्रकारों के अलावा रिटर्न प्रकार और मापदंडों के लिए टाइप जानकारी है। यदि आप इसके बजाय test.C लिखते हैं:

extern "C" {
    void foo() { }
}

फिर संकलन करें और प्रतीकों को देखें:

$ g++ -c test.C
$ nm test.o
                 U __gxx_personality_v0
0000000000000000 T foo

आपको सी लिंकेज मिलता है। ऑब्जेक्ट फ़ाइल में "foo" फ़ंक्शन का नाम सिर्फ "foo" है, और इसमें सभी प्रकार की फैंसी जानकारी नहीं है जो नाम मैनलिंग से आती है।

आप आम तौर पर "C" {} एक्सटर के भीतर एक हेडर शामिल करते हैं यदि इसके साथ जाने वाला कोड C कंपाइलर के साथ संकलित किया गया था लेकिन आप इसे C ++ से कॉल करने का प्रयास कर रहे हैं। जब आप ऐसा करते हैं, तो आप कंपाइलर को बता रहे हैं कि हेडर में सभी घोषणाएं सी लिंकेज का उपयोग करेंगी। जब आप अपने कोड को लिंक करते हैं, तो आपकी .o फ़ाइलों में "foo" के संदर्भ होंगे, न कि "_Z3fooblah", जो कि आपके द्वारा लिंक की जा रही लायब्रेरी में होने वाली आशा से मेल खाता है।

अधिकांश आधुनिक पुस्तकालय ऐसे हेडर के चारों ओर गार्ड लगाएंगे ताकि प्रतीकों को सही लिंकेज के साथ घोषित किया जाए। बहुत से मानक हेडर में आप पाएंगे:

#ifdef __cplusplus
extern "C" {
#endif

... declarations ...

#ifdef __cplusplus
}
#endif

यह सुनिश्चित करता है कि जब C ++ कोड में हेडर शामिल होता है, तो आपकी ऑब्जेक्ट फ़ाइल के सिंबल सी लाइब्रेरी में क्या हैं। यदि आपके पुराने हैं और आपके पास पहले से ही ये गार्ड नहीं हैं, तो आपको केवल अपने C हैडर के चारों ओर "C" {} लगाना होगा।


22

C ++ में, आपके पास एक नाम साझा करने वाली विभिन्न इकाइयाँ हो सकती हैं। उदाहरण के लिए यहां सभी कार्यों की सूची दी गई है जिनका नाम फू है :

  • A::foo()
  • B::foo()
  • C::foo(int)
  • C::foo(std::string)

उन सभी के बीच अंतर करने के लिए, C ++ कंपाइलर प्रत्येक प्रक्रिया के लिए नाम-मनन या सजाने के लिए अद्वितीय नाम बनाएगा। सी कंपाइलर ऐसा नहीं करते हैं। इसके अलावा, प्रत्येक C ++ कंपाइलर ऐसा कर सकता है यह एक अलग तरीका है।

बाहरी "C" सी + + संकलक को ब्रेसिज़ के भीतर कोड पर किसी भी नाम-प्रबंध का प्रदर्शन नहीं करने के लिए कहता है। यह आपको C ++ से C फ़ंक्शन को कॉल करने की अनुमति देता है।


14

यह उस तरह से करना है जैसे विभिन्न संकलक नाम-निर्माण करते हैं। C ++ कंपाइलर हेडर फ़ाइल से निर्यात किए गए एक प्रतीक का नाम एक सी कंपाइलर की तुलना में पूरी तरह से अलग तरीके से रखेगा, इसलिए जब आप लिंक करने का प्रयास करते हैं, तो आपको एक लिंकर त्रुटि मिलेगी, जिसमें कहा गया था कि लापता प्रतीक थे।

इसे हल करने के लिए, हम C ++ कंपाइलर को "C" मोड में चलाने के लिए कहते हैं, इसलिए यह सी कंपाइलर उसी तरह से नाम का प्रदर्शन करता है। ऐसा करने के बाद, लिंकर की त्रुटियां ठीक हो जाती हैं।


11

C और C ++ में प्रतीकों के नामों के बारे में अलग-अलग नियम हैं। प्रतीक यह हैं कि लिंक करने वाला कैसे जानता है कि संकलक द्वारा निर्मित एक ऑब्जेक्ट फ़ाइल में "ओपनबैंकअकाउंट" को फ़ंक्शन करने के लिए कॉल उसी फ़ंक्शन का एक संदर्भ है जिसे आप उसी (या संगत) से अलग स्रोत फ़ाइल से उत्पादित किसी अन्य ऑब्जेक्ट फ़ाइल में "ओपनबैंकअकाउंट" कहते हैं। संकलक। यह आपको एक से अधिक स्रोत फ़ाइल से बाहर एक कार्यक्रम बनाने की अनुमति देता है, जो एक बड़ी परियोजना पर काम करते समय राहत देता है।

सी में नियम बहुत सरल है, प्रतीक वैसे भी सभी एक ही नाम स्थान पर हैं। तो पूर्णांक "मोज़े" को "मोज़े" के रूप में संग्रहीत किया जाता है और फ़ंक्शन count_socks को "count_socks" के रूप में संग्रहीत किया जाता है।

इस सरल प्रतीक नामकरण नियम के साथ सी और सी जैसी अन्य भाषाओं के लिए लिंक बनाए गए थे। तो लिंकर में प्रतीक सिर्फ साधारण तार हैं।

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

C ++ कोड को C लाइब्रेरी या कोड से लिंक करते समय, आपको C में लिखी गई "C" कुछ भी बाहरी चीज़ों की आवश्यकता होती है, जैसे C पुस्तकालयों की हेडर फाइलें, आपके C ++ कंपाइलर को यह बताने के लिए कि इन प्रतीक नामों को मंगवाना नहीं है, जबकि बाकी C आपके C ++ कोड को जरूर मंगवाना चाहिए या यह काम नहीं करेगा।


11

हमें इसका उपयोग कब करना चाहिए?

जब आप C libओं को C ++ ऑब्जेक्ट फ़ाइलों में लिंक कर रहे हैं

संकलक / लिंकर स्तर पर क्या हो रहा है जो हमें इसका उपयोग करने की आवश्यकता है?

C और C ++ प्रतीक नामकरण के लिए विभिन्न योजनाओं का उपयोग करते हैं। यह लिंकर को दी गई लाइब्रेरी में लिंक करते समय सी की योजना का उपयोग करने के लिए कहता है।

संकलन / लिंकिंग के संदर्भ में यह उन समस्याओं को कैसे हल करता है जिनके लिए हमें इसका उपयोग करने की आवश्यकता है?

सी नामकरण योजना का उपयोग करने से आप सी-शैली प्रतीकों का संदर्भ ले सकते हैं। अन्यथा लिंकर C ++ शैली के प्रतीकों की कोशिश करेगा जो काम नहीं करेगा।


7

आपको एक्सटर्नल "C" का उपयोग कभी भी करना चाहिए कि आप C ++ फ़ाइल में उपयोग किए गए C कंपाइलर द्वारा संकलित फ़ाइल में रहने वाले हेडर डिफाइनिंग फ़ंक्शंस को शामिल करें। (कई मानक सी लाइब्रेरी डेवलपर के लिए इसे सरल बनाने के लिए अपने हेडर में इस चेक को शामिल कर सकती हैं)

उदाहरण के लिए, यदि आपके पास 3 फ़ाइलों के साथ एक परियोजना है, तो.c, use.h, और main.cpp और .cpp और .cpp दोनों फाइलें C ++ कंपाइलर (g ++, cc, आदि) के साथ संकलित की जाती हैं, फिर यह isn ' टी वास्तव में जरूरत है, और यहां तक ​​कि लिंकर त्रुटियों का कारण हो सकता है। यदि आपकी निर्माण प्रक्रिया में उपयोग के लिए एक नियमित सी कंपाइलर का उपयोग किया जाता है। तब आपको एक्सटर्नल "सी" का उपयोग करने की आवश्यकता होगी।

क्या हो रहा है कि C ++ अपने नाम में फ़ंक्शन के मापदंडों को एनकोड करता है। इस प्रकार कार्य ओवरलोडिंग काम करता है। सी फ़ंक्शन में आने वाली सभी चीजें अंडरस्कोर ("_") नाम की शुरुआत के अतिरिक्त हैं। एक्सटर्नल "सी" का उपयोग किए बिना लिंकर एक फ़ंक्शन की तलाश करेगा जिसका नाम DoSomething @@ int @ float () है जब फ़ंक्शन का वास्तविक नाम _DoSomething () या सिर्फ DoSomething () है।

बाहरी "C" का उपयोग C ++ कंपाइलर को बताकर उपरोक्त समस्या को हल करता है कि इसे एक फ़ंक्शन की तलाश करनी चाहिए जो C ++ के बजाय C नामकरण सम्मेलन का अनुसरण करता है।


7

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


6

extern "C" {}निर्माण के नाम ब्रेसिज़ के भीतर घोषित पर mangling प्रदर्शन करने के लिए नहीं संकलक निर्देश देता है। आम तौर पर, C ++ कंपाइलर "फ़ंक्शन नाम" को बढ़ाता है ताकि वे तर्क और रिटर्न मान के बारे में जानकारी टाइप करें; इसे मंगली नाम कहा जाता है । extern "C"निर्माण mangling से बचाता है।

इसका उपयोग आमतौर पर तब किया जाता है जब C ++ कोड को C- भाषा लाइब्रेरी को कॉल करने की आवश्यकता होती है। C क्लाइंट के लिए C ++ फ़ंक्शन (उदाहरण के लिए, DLL से) को उजागर करते समय इसका उपयोग किया जा सकता है।


5

इसका उपयोग नाम प्रबंधन की समस्याओं को हल करने के लिए किया जाता है। बाहरी सी का मतलब है कि फ़ंक्शन "फ्लैट" सी-स्टाइल एपीआई में हैं।


0

g++क्या हो रहा है यह देखने के लिए एक उत्पन्न बाइनरी को डिकम्पाइल करें

यह समझने के लिए कि क्यों externआवश्यक है, सबसे अच्छी बात यह समझना है कि उदाहरण के साथ ऑब्जेक्ट फ़ाइलों में विस्तार से क्या हो रहा है:

main.cpp

void f() {}
void g();

extern "C" {
    void ef() {}
    void eg();
}

/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }

जीसीसी 4.8 लिनक्स ईएलएफ आउटपुट के साथ संकलन :

g++ -c main.cpp

प्रतीक तालिका को अपघटित करें:

readelf -s main.o

आउटपुट में शामिल हैं:

Num:    Value          Size Type    Bind   Vis      Ndx Name
  8: 0000000000000000     6 FUNC    GLOBAL DEFAULT    1 _Z1fv
  9: 0000000000000006     6 FUNC    GLOBAL DEFAULT    1 ef
 10: 000000000000000c    16 FUNC    GLOBAL DEFAULT    1 _Z1hv
 11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z1gv
 12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND eg

व्याख्या

हम देखते है कि:

  • efऔर egकोड में समान नाम के साथ प्रतीकों में संग्रहीत किए गए थे

  • अन्य प्रतीकों मंगाई गई थीं। आइए उन्हें अनमंगल करें:

    $ c++filt _Z1fv
    f()
    $ c++filt _Z1hv
    h()
    $ c++filt _Z1gv
    g()

निष्कर्ष: निम्नलिखित दोनों प्रकार के प्रतीक प्रकार नहीं थे :

  • परिभाषित
  • घोषित लेकिन अपरिभाषित ( Ndx = UND), लिंक पर प्रदान करने के लिए या किसी अन्य ऑब्जेक्ट फ़ाइल से समय चलाने के लिए

इसलिए आपको extern "C"कॉल करते समय दोनों की आवश्यकता होगी :

  • C ++ से C: g++द्वारा उत्पादित अनमैंगल्ड प्रतीकों की अपेक्षा करेंgcc
  • C ++ से C: उपयोग करने के g++लिए अनमैन्डल प्रतीक उत्पन्न करना बताएंgcc

बाहरी सी में काम नहीं करने वाली चीजें

यह स्पष्ट हो जाता है कि किसी भी C ++ सुविधा के लिए जिसे नाम की आवश्यकता है, अंदर काम नहीं करेगी extern C:

extern "C" {
    // Overloading.
    // error: declaration of C function ‘void f(int)’ conflicts with
    void f();
    void f(int i);

    // Templates.
    // error: template with C linkage
    template <class C> void f(C i) { }
}

C ++ उदाहरण से न्यूनतम रनवेबल C

पूर्णता के लिए और वहाँ से बाहर के newbs के लिए, यह भी देखें: C ++ प्रोजेक्ट में C स्रोत फ़ाइलों का उपयोग कैसे करें?

C ++ से C को कॉल करना बहुत आसान है: प्रत्येक C फ़ंक्शन में केवल एक संभव गैर-मैनगल्ड प्रतीक है, इसलिए किसी अतिरिक्त कार्य की आवश्यकता नहीं है।

main.cpp

#include <cassert>

#include "c.h"

int main() {
    assert(f() == 1);
}

ch

#ifndef C_H
#define C_H

/* This ifdef allows the header to be used from both C and C++. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif

#endif

सीसी

#include "c.h"

int f(void) { return 1; }

Daud:

g++ -c -o main.o -std=c++98 main.cpp
gcc -c -o c.o -std=c89 c.c
g++ -o main.out main.o c.o
./main.out

extern "C"लिंक के बिना के साथ विफल रहता है:

main.cpp:6: undefined reference to `f()'

क्योंकि g++एक पके हुए आम को खोजने की उम्मीद है f, जो gccउत्पन्न नहीं हुआ।

GitHub पर उदाहरण

C उदाहरण से न्यूनतम रनर C ++

C ++ से कॉल करना थोड़ा कठिन है: हमें मैन्युअल रूप से प्रत्येक फ़ंक्शन के गैर-मैंगल्ड संस्करण बनाने होंगे जिन्हें हम उजागर करना चाहते हैं।

यहाँ हम बताते हैं कि C ++ फंक्शन ओवरलोड को C में कैसे एक्सपोज़ किया जाए।

main.c

#include <assert.h>

#include "cpp.h"

int main(void) {
    assert(f_int(1) == 2);
    assert(f_float(1.0) == 3);
    return 0;
}

cpp.h

#ifndef CPP_H
#define CPP_H

#ifdef __cplusplus
// C cannot see these overloaded prototypes, or else it would get confused.
int f(int i);
int f(float i);
extern "C" {
#endif
int f_int(int i);
int f_float(float i);
#ifdef __cplusplus
}
#endif

#endif

cpp.cpp

#include "cpp.h"

int f(int i) {
    return i + 1;
}

int f(float i) {
    return i + 2;
}

int f_int(int i) {
    return f(i);
}

int f_float(float i) {
    return f(i);
}

Daud:

gcc -c -o main.o -std=c89 -Wextra main.c
g++ -c -o cpp.o -std=c++98 cpp.cpp
g++ -o main.out main.o cpp.o
./main.out

इसके बिना extern "C"विफल रहता है:

main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'

क्योंकि g++उत्पन्न प्रतीक जो gccनहीं मिल सकते हैं।

GitHub पर उदाहरण

उबंटू 18.04 में परीक्षण किया गया।


1
नीचे की व्याख्या के लिए धन्यवाद, यह सब अब समझ में आता है।
सिरो सेंटिल्ली 郝海东 i iro i 法轮功
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.