سؤال

لماذا نحن بحاجة إلى استخدام:

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

على وجه التحديد:

  • متى يجب استخدامه ؟

  • ما يحدث في مترجم/رابط المستوى الذي يتطلب منا أن استخدامه ؟

  • كيف من حيث تجميع/ربط هل هذا حل المشاكل التي تتطلب منا أن استخدامه ؟

هل كانت مفيدة؟

المحلول

C و C++ هي مماثلة بشكل سطحي ، ولكن كل تجمع في مجموعة مختلفة جدا من التعليمات البرمجية.عندما يتضمن الملف رأس مع برنامج التحويل البرمجي C++, مترجم يتوقع رمز C++.إذا ومع ذلك ، فمن ج رأس ، ثم المترجم يتوقع البيانات الواردة في ملف الرأس التي سيتم تجميعها على شكل معين—C++ 'أبي' ، أو 'واجهة تطبيق ثنائية' لذا رابط يخنق حتى.هذا هو الأفضل أن يمر C++ البيانات إلى وظيفة أتوقع ج البيانات.

(للوصول الى الحقيقة الجوهرية ، C++'s أبي عموما 'mangles' أسماء وظائفهم/طرق ، لذلك يدعو printf() دون إبراز النموذج بوصفها وظيفة C, C++ سوف تولد في الواقع رمز الدعوة _Zprintf, بالإضافة إلى إضافية حماقة في نهاية المطاف.)

لذلك:استخدام extern "C" {...} عندما بما في ذلك ج رأس—أن الأمر بسيط.وإلا سيكون لديك عدم تطابق في التعليمات البرمجية المترجمة ، linker خنق.معظم رؤوس ، ومع ذلك ، فلن تحتاج حتى extern لأن معظم نظام C رؤوس تمثل بالفعل حقيقة أنها يمكن إدراجها من قبل رمز C++ و بالفعل extern التعليمات البرمجية الخاصة بهم.

نصائح أخرى

extern "C" يحدد كيفية الرموز في إنشاء كائن يجب أن تكون تسمية الملف.إذا كانت الوظيفة المعلنة دون extern "C" ، الرمز اسم في ملف الكائن سوف تستخدم C++ اسم من الضغط.هنا مثال.

اختبار معين.ج مثل ذلك:

void foo() { }

تجميع قائمة الرموز في ملف الكائن يعطي:

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

وظيفة فو هو في الواقع يسمى "_Z3foov".هذه السلسلة تحتوي على معلومات نوع نوع الإرجاع والمعلمات ، من بين أمور أخرى.إذا كنت بدلا من ذلك كتابة الاختبار.ج مثل هذا:

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

ثم ترجمة ثم ننظر الرموز:

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

يمكنك الحصول على ج الربط.اسم "فو" وظيفة في ملف الكائن هو مجرد "فو" ، وأنه لا يملك كل الهوى نوع المعلومات التي تأتي من اسم من الضغط.

عموما تشمل رأس داخل extern "C" {} إذا كان رمز أن يذهب معها جمعت مع برنامج التحويل البرمجي C ولكن كنت أحاول الاتصال من C++.عند القيام بذلك, أنت تقول المترجم أن جميع الإعلانات في رأس سوف تستخدم C الربط.عند ربط التعليمات البرمجية الخاصة بك .o ملفات تحتوي على مراجع إلى "فو" ، وليس "_Z3fooblah" ، والتي نأمل أن تطابق ما في المكتبة أنت ربط ضد.

معظم المكتبات الحديثة سوف يضع الحراس حول هذه الرؤوس بحيث الرموز المعلنة مع الحق في الربط.على سبيل المثالفي الكثير من رؤوس القياسية ستجد:

#ifdef __cplusplus
extern "C" {
#endif

... declarations ...

#ifdef __cplusplus
}
#endif

وهذا يجعل التأكد من أن عند C++ قانون يتضمن رأس الرموز في ملف الكائن مباراة ما في المكتبة.يجب أن يكون لديك فقط لوضع extern "C" {} حول ج رأس إذا كان القديم لا يكون هؤلاء الحراس بالفعل.

في C++, هل يمكن أن يكون مختلف الكيانات التي تشترك في الاسم.على سبيل المثال هنا هو قائمة مهام كل مسمى فو:

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

من أجل التفريق بين كل منهم ، برنامج التحويل البرمجي C++ إنشاء أسماء فريدة من نوعها لكل في عملية تسمى اسم-تغيير اسم أو تزيين.ج المجمعين لا تفعل هذا.وعلاوة على ذلك, كل برنامج التحويل البرمجي C++ قد فعل هذا هو وسيلة مختلفة.

extern "C" يروي برنامج التحويل البرمجي C++ عدم القيام بأي اسم-الضغط على رمز داخل الأقواس.هذا يسمح لك استدعاء ج المهام من خلال C++.

عليها أن تفعل مع طريقة مختلفة المجمعين أداء الاسم-تغيير اسم.C++ compiler سوف فسد اسم رمز تصديرها من ملف الرأس بطريقة مختلفة تماما من برنامج التحويل البرمجي C ، لذا عند محاولة الرابط ستحصل على رابط خطأ بأن هناك عداد المفقودين الرموز.

لحل هذه ، ونحن نقول برنامج التحويل البرمجي C++ تشغيل في "ج" واسطة ، لذلك ينفذ اسم من الضغط في نفس طريقة التحويل البرمجي C شأنه.وقد فعلت ذلك, رابط الأخطاء الثابتة.

متى يجب استخدامه ؟

عندما يتم ربط ج libaries في C++ ملفات الكائن

ما يحدث في مترجم/رابط المستوى الذي يتطلب منا لاستخدامه ؟

C و C++ استخدام مخططات مختلفة عن الرمز التسمية.يقول هذا رابط لاستخدام ج المخطط عندما ربط في المكتبة.

كيف من حيث تجميع/ربط هل هذا حل المشاكل التي تتطلب منا أن استخدامه ؟

باستخدام C تسمية مخطط يسمح لك مرجع ج-نمط الرموز.وإلا رابط سيحاول C++-نمط الرموز التي لا تعمل.

C و C++ لها قواعد مختلفة عن أسماء الرموز.الرموز هي كيف linker يعلم أن الدعوة إلى وظيفة "openBankAccount" في كائن واحد ملف المنتجة من قبل المترجم هو إشارة إلى أن وظيفة ما يسمى "openBankAccount" في كائن آخر الملفات التي تنتج من ملف مصدر آخر من نفس (أو متوافقة) المترجم.هذا يسمح لك لجعل البرنامج أكثر من ملف مصدر واحد ، وهو الإغاثة عند العمل على مشروع كبير.

في ج القاعدة بسيطة جدا والرموز كلها في مساحة اسم واحد على أي حال.وبالتالي فإن عدد صحيح "الجوارب" مخزنه "الجوارب" وظيفة count_socks هو تخزين "count_socks".

Linkers بنيت C لغات أخرى مثل C مع هذا بسيطة رمز تسمية القاعدة.حتى الرموز في رابط بسيطة فقط السلاسل.

ولكن في C++ لغة تمكنك من الحصول على مساحات الأسماء و الأشكال و العديد من الأشياء الأخرى التي تتعارض مع هذه القاعدة البسيطة.كل ستة من الأشكال وظائف يسمى "إضافة" بحاجة إلى رموز مختلفة أو خطأ واحد سيتم استخدامها من قبل كائن آخر الملفات.ويتم ذلك عن طريق "الضغط" (هذا المصطلح) أسماء الرموز.

عند ربط رمز C++ C المكتبات أو رمز ، تحتاج extern "C" أي شيء مكتوب في C ، مثل ملفات رأس C المكتبات أن أقول C++ compiler أن هذه أسماء رمز لا يكون المهترئ ، في حين أن بقية C++ code بالطبع يجب أن تكون مشوهة أو أنها لن تنجح.

يجب عليك استخدام extern "C" في أي وقت أن تقوم بتضمين رأس تعريف وظائف المقيمين في ملف جمعت من قبل برنامج التحويل البرمجي C, تستخدم في C++ الملف.(العديد من معيار ج المكتبات قد تشمل الاختيار هذا في رؤوس لجعله أسهل بالنسبة المطور)

على سبيل المثال, إذا كان لديك مشروع مع 3 ملفات, util.ج ، util.ح ، main.cpp و على حد سواء .ج .cpp يتم تجميع الملفات مع برنامج التحويل البرمجي C++ (g++, cc, الخ) ثم ليست هناك حاجة حقا ، وحتى قد تسبب أخطاء رابط.إذا كان الخاص بك بناء العملية يستخدم العادية ج مترجم util.ج, ثم سوف تحتاج إلى استخدام extern "C" عندما بما في ذلك util.ح.

ما يحدث هو أن C++ بترميز المعلمات وظيفة في اسمها.هذا هو كيف دالة التحميل الزائد يعمل.كل ذلك يميل إلى أن يحدث C وظيفة إضافة تسطير أسفل السطر ("_") إلى بداية الاسم.دون استخدام extern "C" رابط سوف تبحث عن وظيفة اسمه DoSomething@@int@تعويم() عندما الوظيفة الفعلية اسم _DoSomething() أو مجرد DoSomething().

باستخدام extern "C" يحل المشكلة أعلاه بقوله برنامج التحويل البرمجي C++ أنه ينبغي البحث عن وظيفة على النحو التالي C اصطلاح التسمية بدلا من C++ واحدة.

برنامج التحويل البرمجي C++ يخلق رمز الأسماء بشكل مختلف عن برنامج التحويل البرمجي C.لذا ، إذا كنت تحاول إجراء مكالمة إلى وظيفة موجود في الملف C, تجميع التعليمات البرمجية C, عليك أن تخبر برنامج التحويل البرمجي C++ هذا الرمز أسماء أنها تحاول حل تبدو مختلفة من التخلف ؛ وإلا الرابط خطوة سوف تفشل.

على extern "C" {} بناء يرشد مترجم عدم تنفيذ الضغط على الأسماء المعلنة داخل الأقواس.عادة, برنامج التحويل البرمجي C++ "يعزز" على أسماء الوظائف بحيث ترميز نوع المعلومات حول الحجج و عودة القيمة ؛ وهذا ما يسمى المهترئ اسم.على extern "C" بناء يمنع تغيير اسم.

عادة ما تستخدم عند C++ رمز يحتاج إلى استدعاء ج-مكتبة اللغة.فإنه يمكن أيضا أن تستخدم عند تعريض C++ وظيفة (من DLL ، على سبيل المثال) إلى ج العملاء.

يستخدم هذا حل اسم من الضغط القضايا.extern ج يعني أن الوظائف هي في "شقة" C API.

فك a g++ ولدت الثنائية لمعرفة ما يجري

أنا تتحرك في هذا الجواب من: ما هو تأثير extern "C" في C++? منذ أن السؤال كان يعتبر نسخة مكررة من هذا واحد.

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 تم تخزينها في الرموز مع نفس الاسم مثل في كود

  • غيرها من الرموز كانت المهترئ.دعونا unmangle لهم:

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

الخلاصة:كل من الرموز التالية أنواع لا المهترئ:

  • تعريف
  • وأعلن ولكن غير معروف (Ndx = UND) ، في الرابط أو وقت التشغيل من كائن آخر الملف

لذلك سوف تحتاج extern "C" سواء عند الاتصال:

  • C C++:اقول g++ نتوقع unmangled الرموز التي تنتجها gcc
  • C++ من C:اقول g++ لتوليد unmangled رموز gcc استخدام

الأشياء التي لا تعمل في extern ج

يصبح من الواضح أن أي 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) { }
}

الحد الأدنى runnable C C++ سبيل المثال

لأجل اكتمال عن newbs هناك ، انظر أيضا: كيفية استخدام المصدر C الملفات في C++ المشروع ؟

داعيا C C++ هي سهلة جدا:كل وظيفة C فقط ممكن واحد غير المهترئ رمز لذا أي عمل إضافي مطلوب.

main.cpp

#include <cassert>

#include "c.h"

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

ج.ح

#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; }

تشغيل:

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 لم تنتج.

مثال على جيثب.

الحد الأدنى runnable C++ من C مثلا

داعيا C++ من هو أصعب قليلا:يجب أن يدويا إنشاء غير المهترئ إصدارات كل وظيفة نريد فضح.

نحن هنا توضح كيفية كشف C++ وظيفة الزائدة إلى C.

الرئيسية.ج

#include <assert.h>

#include "cpp.h"

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

cpp.ح

#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);
}

تشغيل:

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 لا يمكن العثور على.

مثال على جيثب.

اختبار في أوبونتو 18.04.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top