ما هو تأثير extern "C" في C++?
-
22-07-2019 - |
سؤال
ماذا يفعل بالضبط وضع extern "C"
في C++ كود تفعل ؟
على سبيل المثال:
extern "C" {
void foo();
}
المحلول
extern "C" يجعل وظيفة الاسم في C++ يكون 'ج' الربط (مترجم لا فسد اسم) بحيث العميل كود C يمكن أن تصل إلى (أنا.e استخدام) وظيفة الخاص بك باستخدام 'ج' متوافق رأس الملف الذي يحتوي فقط على إعلان وظيفة الخاص بك.وظيفة التعريف الوارد في تنسيق ثنائي (التي تم تجميعها بواسطة برنامج التحويل البرمجي C++) أن العميل 'ج' رابط ثم الارتباط باستخدام 'ج' اسم.
منذ C++ وقد الحمولة الزائدة الدالة أسماء C, C++ compiler لا يمكن فقط استخدام الدالة اسم معرف فريد رابط, لذلك mangles اسم بإضافة معلومات عن الحجج.ج مترجم لا تحتاج إلى فسد الاسم منذ أن كنت لا يمكن أن تفرط وظيفة الأسماء في C.عند القول أن وظيفة extern "C" الربط في C++, C++ compiler لا تضيف الحجة/المعلمة نوع المعلومات إلى اسم المستخدمة في الربط.
لعلمك يمكنك تحديد "ج" الربط لكل فرد إعلان/تعريف صراحة أو استخدام كتلة المجموعة سلسلة من الإعلانات/تعريفات بعض الروابط:
extern "C" void foo(int);
extern "C"
{
void g(char);
int i;
}
إذا كنت الرعاية حول الجوانب التقنية ، فهي المدرجة في القسم 7.5 C++03 المعيار هنا هو ملخص موجز (مع التركيز على extern "C"):
- extern "C" هو الربط-مواصفات
- كل مترجم مطلوب إلى تقديم "ج" الربط
- الربط مواصفات لن يحدث إلا في مساحة اسم النطاق
وظيفة كل أنواع وظيفة أسماء و أسماء المتغيرات لغة الربطانظر ريتشارد التعليق: وظيفة فقط أسماء و أسماء المتغيرات الخارجية الربط لغة الربط- اثنين وظيفة أنواع متميزة مع اللغة الروابط أنواع متميزة حتى لو متطابقة
- الربط المواصفات عش واحد الداخلية يحدد الربط النهائي
- extern "C" هو تجاهل أعضاء الفئة
- في أكثر من وظيفة واحدة مع اسم معين يمكن أن يكون "ج" الربط (بغض النظر عن الاسم)
extern "C" القوات وظيفة أن يكون الخارجية الربط (لا تجعل ثابت)انظر ريتشارد التعليق: 'ثابت' داخل 'extern "C"' صالحة ؛ كيان حتى أعلنت الداخلية الربط ، وذلك ليس لديها لغة الربط- الربط من C++ إلى كائنات محددة في اللغات الأخرى إلى كائنات محددة في C++ من لغات أخرى هو تنفيذ محددة تعتمد لغة.إلا إذا كان كائن تخطيط استراتيجيات اثنين اللغة تطبيقات مماثلة بما فيه الكفاية يمكن أن يتحقق هذا الربط
نصائح أخرى
وأردت فقط أن إضافة القليل من المعلومات، وبما أنني لم أر ذلك تنشر بعد.
وعليك في كثير من الأحيان نرى التعليمات البرمجية في رؤوس C مثل ذلك:
#ifdef __cplusplus
extern "C" {
#endif
// all of your legacy C code here
#ifdef __cplusplus
}
#endif
وهذا ما يحقق هو أنه يتيح لك استخدام هذا C ملف الرأس مع التعليمات البرمجية C ++، لأن الماكرو "__cplusplus" سيتم تحديد. ولكن يمكنك <م> أيضا م> الاستمرار في استخدامه مع حسابك القديم كود C، حيث الماكرو <م> لا م> محددة، لذلك لن ترى فريد C ++ بناء.
وعلى الرغم من أنني لم أر أيضا ++ كود C مثل:
extern "C" {
#include "legacy_C_header.h"
}
وأنا وهو كهاتين يحقق الكثير من الشيء نفسه.
ولست متأكدا الطريق الذي هو أفضل، ولكن رأيت على حد سواء.
في كل برنامج C ++، يتم تمثيل جميع وظائف غير ثابتة في ملف ثنائي كرموز. هذه الرموز هي سلاسل نصية خاصة التي تنفرد بتحديد وظيفة في البرنامج.
في C، اسم الرمز هو نفس اسم الدالة. وهذا ممكن لأن في C أي اثنين وظائف غير ثابتة يمكن أن يكون لها نفس الاسم.
ولأن C ++ يسمح الزائد، ولها العديد من الميزات التي C لا - فئات مثل، وظائف الأعضاء، مواصفات استثناء - أنه ليس من الممكن ببساطة استخدام اسم الدالة كاسم الرمز. لحل ذلك، يستخدم C ++ ما يسمى تغيير اسم الاسم، والذي يحول اسم الدالة وجميع المعلومات اللازمة (مثل عدد وحجم الحجج) في بعض سلسلة أبحث غريبة المصنعة فقط من قبل مترجم ورابط.
وحتى إذا كنت تحديد وظيفة ليكون خارجي ... C، المترجم لا يؤدي تغيير اسم اسم معها وأنها يمكن أن تكون مباشرة الوصول إليها باستخدام اسم رمزها كاسم وظيفة.
وهذا الأمر مفيد أثناء استخدام dlsym()
وdlopen()
لاستدعاء مثل هذه الوظائف.
فك a g++
ولدت الثنائية لمعرفة ما يجري
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++ وظيفة الزائدة إلى 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.
C ++ mangles أسماء وظيفة لخلق لغة وجوه المنحى من لغة إجرائية
لا تبنى ومعظم لغات البرمجة على رأس لغات البرمجة الحالية. بنيت C ++ على رأس C، وعلاوة على انها لغة برمجة كائنية التوجه بنيت من لغة البرمجة الإجرائية، ولهذا السبب هناك تعبيرات C ++ مثل extern "C"
التي توفر التوافق مع C.
ودعونا ننظر في المثال التالي:
#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;
}
وسوف مترجم C لا ترجمة المثال أعلاه، لأنه يتم تعريف نفسها وظيفة printMe
مرتين (على الرغم من أنها لديها مختلفة المعلمات int a
مقابل char a
).
ودول مجلس التعاون الخليجي -o printMe printMe.c && ./printMe،
1 خطأ. ويعرف PrintMe أكثر من مرة واحدة. م>
وA C ++ مترجم ستجمع المثال أعلاه. أنه لا يهتم بأن يعرف printMe
مرتين.
وز ++ -o printMe printMe.c && ./printMe؛
اقتباس فقرة>وذلك لأن مترجم C ++ إعادة تسمية ضمنا ( mangles ) وظائف على أساس المعلمات الخاصة بهم. في C، وهذه الميزة غير مدعومة. ومع ذلك، عندما تم بناء C ++ على C، وقد تم تصميم اللغة لتكون وجوه المنحى، واللازمة لدعم القدرة على خلق فئات مختلفة مع أساليب (وظائف) الذي يحمل نفس الاسم، وتجاوز الأساليب (<لأ href = "HTTPS : //en.wikipedia.org/wiki/Method_overriding "يختلط =" نوفولو noreferrer "> طريقة تجاوز ) استنادا إلى معايير مختلفة
extern "C"
يقول "لا فسد أسماء C وظيفة"
ولكن، تخيل لدينا ملف الإرث C المسمى "parent.c" أن أسماء include
s وظيفة من غيرها من الملفات إرث C "parent.h"، "child.h"، وما إذا كان إرث "parent.c" يتم تشغيل الملف من خلال C ++ المترجم، ثم سيتم أخطأ في أسماء وظيفة، وأنها لم تعد تطابق أسماء دالة المحدد في "parent.h"، "child.h"، وما إلى ذلك - حتى أسماء وظيفة في تلك الملفات الخارجية من شأنه تحتاج أيضا إلى المهترئ. تغيير اسم أسماء وظيفة عبر برنامج C معقدة، وتلك مع الكثير من التبعيات، يمكن أن يؤدي إلى رمز كسر. لذلك قد تكون مريحة لتقديم الكلمة التي يمكن أن نقول للمترجم C ++ لا فسد اسم دالة.
والكلمة extern "C"
يقول مترجم C ++ لا فسد (تسمية) أسماء C وظيفة. استخدام المثال: extern "C" void printMe(int a);
ويتغير الربط بين وظيفة في مثل هذه الطريقة التي هي وظيفة للاستدعاء من C. في الممارسة العملية وهذا يعني أن اسم الوظيفة ليست <وأ href = "http://en.wikipedia.org/wiki/Name_mangling "يختلط =" noreferrer "> المهترئ.
لا يمكن إجراء أي C-رأس متوافقة مع C ++ بمجرد التفاف في خارجي ... "C". عندما معرفات في صراع C-رأس مع C ++ الكلمات الرئيسية مترجم C ++ سوف يشكو حول هذا الموضوع.
وعلى سبيل المثال، لقد رأيت التعليمة البرمجية التالية تفشل في غرام ++:
extern "C" {
struct method {
int virtual;
};
}
وكندة المنطقي، ولكن هو شيء أن نأخذ في الاعتبار عند ترقية C-الرمز إلى C ++.
ويبلغ مترجم C ++ للبحث عن أسماء تلك الوظائف من طراز C عند ربط، لأن أسماء الوظائف التي جمعت في C و C ++ مختلفة خلال مرحلة الارتباط.
وخارجي "C" لا بد من الاعتراف من قبل مترجم C ++ وإخطار المترجم أن وظيفة لاحظت هي (أو أن يكون) جمعت في النمط C. ذلك أنه في حين ربط، وتصل إلى الإصدار الصحيح من وظيفة من C.
ولقد استخدمت "خارجي" C "" قبل لدلل الملفات (مكتبة الارتباط الحيوي) لجعل الخ الوظيفة الرئيسية () "للتصدير" لذلك يمكن استخدامها في وقت لاحق في تنفيذ آخر من DLL. ربما مثالا من حيث اعتدت على استخدامها يمكن أن تكون مفيدة.
و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);
}
وEXE
#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"
هو الربط المواصفات التي تستخدم في استدعاء وظائف C في Cpp الملفات المصدر.يمكننا استدعاء وظائف C, كتابة المتغيرات ، & وتشمل رؤوس.وظيفة أعلنت في extern كيان & ويعرف من الخارج.بناء الجملة هو
النوع 1:
extern "language" function-prototype
النوع 2:
extern "language"
{
function-prototype
};
على سبيل المثال:
#include<iostream>
using namespace std;
extern "C"
{
#include<stdio.h> // Include C Header
int n; // Declare a Variable
void func(int,int); // Declare a function (function prototype)
}
int main()
{
func(int a, int b); // Calling function . . .
return 0;
}
// Function definition . . .
void func(int m, int n)
{
//
//
}
هذا الجواب هو الصبر/ المواعيد النهائية لتلبية فقط جزء/تفسير بسيط هو أدناه:
- في C++, هل يمكن أن يكون نفس الاسم في الصف عن طريق التحميل الزائد (على سبيل المثال ، لأنها كلها بنفس الاسم لا يمكن تصديرها من dll, etc.) حل هذه المشاكل يتم تحويلها إلى سلاسل مختلفة (تسمى الرموز), رموز الحسابات اسم الوظيفة أيضا الحجج ، لذلك كل من هذه المهام حتى مع نفس الاسم ، يمكن تحديدها بشكل فريد (وتسمى أيضا اسم من الضغط)
- في " ج " لا يجب الزائد ، اسم وظيفة فريدة من نوعها (لذلك ، سلسلة منفصلة من أجل تحديد وظيفة اسم فريد غير مطلوب ، لذلك الرمز هو اسم الدالة نفسها)
لذلك
في C++, مع اسم تغيير اسم فريد هويات كل وظيفة
في C ، حتى من دون اسم تغيير اسم فريد هويات كل وظيفة
إلى تغيير سلوك C++ ، أي أن تحديد هذا الاسم من الضغط لا ينبغي أن يحدث عن وظيفة معينة ، يمكنك استخدام extern "C" قبل اسم الدالة ، لأي سبب من الأسباب, مثل تصدير وظيفة مع اسم معين من dll ، للاستخدام من قبل العملاء.
قراءة أخرى إجابات أكثر تفصيلا/أكثر الإجابات الصحيحة.
عند خلط C و C ++ (أي لاستدعاء الدالة C من C ++؛ و (ب) يدعو C ++ وظيفة من C)، واسم C ++ تغيير اسم أسباب ربط المشاكل. من الناحية الفنية، هذه المسألة يحدث فقط عندما تكون وظائف المستدعي تم تجميعها بالفعل إلى ثنائي (على الأرجح، و* اللا ملف مكتبة) باستخدام مترجم المقابلة.
لذلك نحن بحاجة إلى استخدام خارجي "C" لتعطيل اسم تغيير اسم في C ++.