c++ - सी++ में बाहरी "सी" का प्रभाव क्या है?




linkage name-mangling (8)

एक प्रक्रियात्मक भाषा से ऑब्जेक्ट उन्मुख भाषा बनाने के लिए C ++ मैंगल फ़ंक्शन नाम

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

आइए निम्नलिखित उदाहरण देखें:

#include <stdio.h>

// Two functions are defined with the same name
// but have different parameters

void printMe(int a) {
  printf("int: %i\n", a);
}

void printMe(char a) {
  printf("char: %c\n", a);
}

int main() {
  printMe("a");
  printMe(1);
  return 0;
}

एसी कंपाइलर उपर्युक्त उदाहरण संकलित नहीं करेगा, क्योंकि एक ही फ़ंक्शन printMe को दो बार परिभाषित किया गया है (भले ही उनके पास बनाम char a अलग-अलग पैरामीटर हों)।

gcc -o printMe printMe.c && ./printMe;
1 त्रुटि PrintMe एक से अधिक बार परिभाषित किया गया है।

एक सी ++ संकलक उपर्युक्त उदाहरण संकलित करेगा। यह परवाह नहीं है कि printMe दो बार परिभाषित किया गया है।

g ++ -o printMe printMe.c && ./printMe;

ऐसा इसलिए है क्योंकि एक सी ++ कंपाइलर अपने पैरामीटर के आधार पर निहित रूप से नाम बदलता है ( en.wikipedia.org/wiki/Name_mangling )। सी में, यह सुविधा समर्थित नहीं थी। हालांकि, जब सी ++ सी पर बनाया गया था, तो भाषा को ऑब्जेक्ट उन्मुख होने के लिए डिज़ाइन किया गया था, और उसी नाम के तरीकों (कार्यों) के साथ विभिन्न वर्गों को बनाने की क्षमता का समर्थन करने के लिए और अलग-अलग तरीकों के आधार पर विधियों ( विधि ओवरराइडिंग ) को ओवरराइड करने की आवश्यकता थी। मापदंडों।

बाहरी कहते हैं, "फ़ंक्शन नामों को उलझाना न करें"

हालांकि, कल्पना करें कि हमारे पास "parent.c" नामक एक विरासत सी फ़ाइल है include अन्य विरासत सी फाइलों, "parent.h", "child.h", आदि से फ़ंक्शन नाम include । यदि विरासत "parent.c" फ़ाइल है एक C ++ कंपाइलर के माध्यम से चलाएं, फिर फ़ंक्शन नामों को उलझाया जाएगा, और वे अब "parent.h", "child.h", आदि में निर्दिष्ट फ़ंक्शन नामों से मेल नहीं खाएंगे - इसलिए उन बाहरी फ़ाइलों में फ़ंक्शन नामों की आवश्यकता होगी साथ ही उलझन में रहो। और यह काफी गन्दा हो सकता है। तो यह एक ऐसा कीवर्ड प्रदान करना सुविधाजनक हो सकता है जो C ++ कंपाइलर को फ़ंक्शन नाम को मैंग न करने के लिए कह सके।

extern कीवर्ड एक C ++ संकलक को फ़ंक्शन नामों को उलझाने (नाम बदलने) नहीं बताता है। उदाहरण उपयोग: extern void printMe(int a);

क्या सी ++ कोड में extern "C" डालने से वास्तव में क्या होता है?

उदाहरण के लिए:

extern "C" {
   void foo();
}

आइए इस क्रियान्वयन के अंदर क्या चल रहा है यह देखने के लिए उत्पन्न ऑब्जेक्ट फ़ाइल g ++ को संकुचित करें।

उदाहरण उत्पन्न करें

इनपुट:

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 a.cpp

प्रतीक तालिका को कम करें:

readelf -s a.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" दोनों की आवश्यकता होगी:

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

चीजें जो बाहरी सी में काम नहीं करती हैं

यह स्पष्ट हो जाता है कि किसी भी सी ++ सुविधा के लिए नाम उलझन की आवश्यकता है 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) { }
}

प्रत्येक सी ++ प्रोग्राम में, सभी गैर स्थैतिक कार्यों को बाइनरी फ़ाइल में प्रतीक के रूप में दर्शाया जाता है। ये प्रतीकों विशेष टेक्स्ट स्ट्रिंग्स हैं जो प्रोग्राम में किसी फ़ंक्शन को विशिष्ट रूप से पहचानते हैं।

सी में, प्रतीक नाम फ़ंक्शन नाम के समान होता है। यह संभव है क्योंकि सी में दो गैर स्थैतिक कार्यों का एक ही नाम हो सकता है।

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

इसलिए यदि आप बाहरी सी होने के लिए कोई फ़ंक्शन निर्दिष्ट करते हैं, तो संकलक इसके साथ नाम मैंगलिंग नहीं करता है और इसे फ़ंक्शन नाम के रूप में इसके प्रतीक नाम का उपयोग करके सीधे एक्सेस किया जा सकता है।

ऐसे कार्यों को कॉल करने के लिए dlsym() और dlopen() का उपयोग करते समय यह आसान होता है।


बस थोड़ी सी जानकारी जोड़ना चाहती थी, क्योंकि मैंने इसे अभी तक पोस्ट नहीं देखा है।

आप अक्सर सी हेडर में कोड देखेंगे जैसे:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

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

हालांकि, मैंने सी ++ कोड भी देखा है जैसे कि:

extern "C" {
#include "legacy_C_header.h"
}

जो मैं कल्पना करता हूं कि वही चीज़ पूरी करती है।

सुनिश्चित नहीं है कि कौन सा तरीका बेहतर है, लेकिन मैंने दोनों को देखा है।


बाहरी "सी" सी ++ में फ़ंक्शन-नाम बनाता है 'सी' लिंकेज (कंपाइलर नाम को उलझन में नहीं करता है) ताकि क्लाइंट सी कोड एक 'सी' संगत हेडर फ़ाइल का उपयोग करके आपके फ़ंक्शन से लिंक कर सके (यानी उपयोग करें) जिसमें केवल आपके समारोह की घोषणा आपकी फ़ंक्शन परिभाषा एक बाइनरी प्रारूप में निहित है (जिसे आपके सी ++ कंपाइलर द्वारा संकलित किया गया था) कि क्लाइंट 'सी' लिंकर तब 'सी' नाम का उपयोग करने के लिए लिंक करेगा।

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

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

extern "C" void foo(int);
extern "C"
{
   void g(char);
   int i;
}

यदि आप तकनीकीताओं की परवाह करते हैं, तो वे सी ++ 03 मानक की धारा 7.5 में सूचीबद्ध हैं, यहां एक संक्षिप्त सारांश है (बाहरी "सी" पर जोर देने के साथ):

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

मैंने डीएल (गतिशील लिंक लाइब्रेरी) फ़ाइलों को बनाने के लिए पहले 'बाहरी "सी' 'का उपयोग किया था। मुख्य () फ़ंक्शन" निर्यात योग्य "ताकि इसे बाद में किसी अन्य निष्पादन योग्य में डीएल से उपयोग किया जा सके। शायद इसका एक उदाहरण जहां मैं इसका उपयोग करता था, उपयोगी हो सकता है।

DLL

#include <string.h>
#include <windows.h>

using namespace std;

#define DLL extern "C" __declspec(dllexport)
//I defined DLL for dllexport function
DLL main ()
{
    MessageBox(NULL,"Hi from DLL","DLL",MB_OK);
}

प्रोग्राम फ़ाइल

#include <string.h>
#include <windows.h>

using namespace std;

typedef LPVOID (WINAPI*Function)();//make a placeholder for function from dll
Function mainDLLFunc;//make a variable for function placeholder

int main()
{
    char winDir[MAX_PATH];//will hold path of above dll
    GetCurrentDirectory(sizeof(winDir),winDir);//dll is in same dir as exe
    strcat(winDir,"\\exmple.dll");//concentrate dll name with path
    HINSTANCE DLL = LoadLibrary(winDir);//load example dll
    if(DLL==NULL)
    {
        FreeLibrary((HMODULE)DLL);//if load fails exit
        return 0;
    }
    mainDLLFunc=(Function)GetProcAddress((HMODULE)DLL, "main");
    //defined variable is used to assign a function from dll
    //GetProcAddress is used to locate function with pre defined extern name "DLL"
    //and matcing function name
    if(mainDLLFunc==NULL)
    {
        FreeLibrary((HMODULE)DLL);//if it fails exit
        return 0;
    }
    mainDLLFunc();//run exported function 
    FreeLibrary((HMODULE)DLL);
}

यह सी ++ कंपाइलर को लिंक करते समय सी-स्टाइल में उन कार्यों के नामों को देखने के लिए सूचित करता है, क्योंकि सी और सी ++ में संकलित कार्यों के नाम लिंकिंग चरण के दौरान अलग होते हैं।


सी और सी ++ (यानी, ए। सी ++ से सी कॉलिंग फंक्शन और सी से सी ++ फ़ंक्शन को कॉल करते समय), सी ++ नाम मैंगलिंग समस्याओं को जोड़ने का कारण बनता है। तकनीकी रूप से बोलते हुए, यह समस्या तभी होती है जब संबंधित कंपाइलर का उपयोग करके कैली फ़ंक्शन पहले ही बाइनरी (सबसे अधिक संभावना है, * *। लाइब्रेरी फ़ाइल) में संकलित हो चुके हैं।

तो हमें सी ++ में नाम मैंगलिंग को अक्षम करने के लिए बाहरी "सी" का उपयोग करने की आवश्यकता है।







extern-c