السلام عليكم ...
مررت عدة مرات في اليومين الماضيين على عبارة
extern "C"
{ثم يبدأ بعدها تعريف المكتبة أو عمليات معينة ...
سؤالي قسمان :
القسم الأول : كيف نستخدم التعليمة extern ?
القسم الثاني : ما معنى العبارة في الكود السابق ؟
وشكرا ..
السلام عليكم ...
مررت عدة مرات في اليومين الماضيين على عبارة
extern "C"
{ثم يبدأ بعدها تعريف المكتبة أو عمليات معينة ...
سؤالي قسمان :
القسم الأول : كيف نستخدم التعليمة extern ?
القسم الثاني : ما معنى العبارة في الكود السابق ؟
وشكرا ..
راجع هذه المشاركة.
C++ and Java, say, are presumably growing faster than plain C, but I bet C will still be around. ― Dennis Ritchie
~OoO~________--------------------------------------------________~OoO~
من مواضيعي :
اقتباسالقسم الأول : كيف نستخدم التعليمة extern ?
داخل لغة ++C يوجد نوعين من linkage هما internal linkage و external linkage.
المقصود بعملية الربط للمتغيرات هو طبيعة المساحة التخزينية التى سيتم التعامل معها فهل هي مختلفة من ملف لأخر ام انها نفس المساحة التخزينية لكل الملفات؟
عندما تقوم بتعريف متغيرات عامه داخل translation unit بإستخدام الكلمة static فأنت تعلم المترجم ان المساحة التخزينية التى سيتم إنشائها لهم تمثل هذه المتغيرات فقط و أن أى translation unit أخرى يمكنها إستخدام نفس إسم المتغير للإعلان عن مساحات تخزينية خاصة بهم. إستخدام الكلمة static أثناء تعريف او التصريح عن المتغير العام يعني أنه سيتم إستخدام الـ internal linkage له.
الوضع الإفتراضي للمتغيرات العامه داخل الـ translation unit يكون external linkage و هذا يعنى أن المتغيرات العامه كلها تكون بـ extern سواء كتبت او لا.
جميع المتغيرات العامه يتم حجز لها مساحة سواء قمت بوضع قيمة مبدئية لها او لم تضعهم و بالتالي إذا كان لديك ملفان translation units و بهم متغيران عامان بنفس الإسم ستنجح عملية الترجمه و لكن الـ linker سيفشل لوجود متغيرين بنفس الإسم و بالتالي إحداهما لابد من ان يتم الإعلان عنه بـ extern و ان يكون تصريح فقط أى لا تضع به قيمة لكي تنجح العملية.
مثال 1:
// file a.cc int a = 10; // file b.cc int a;
هذا المثال خاطئ لأن كلا المتغيرين سيتم تعريف مساحة تخزينية لهم و لكلاهما له external linkage و بالتالي الـ linker سيعترض على وجود مساحتين تخزينيتين لنفس الإسم.
مثال 2:
// file a.cc extern int a = 10; // file b.cc int a;
هذا المثال مثل السابق و الفرق انك وضعت extern بشكل صريح.
مثال 3:
// file a.cc int a = 10; // file b.cc extern int a;
هذا المثال صحيح حيث سيتم إنشاء المساحة التخزينية بالملف a و سيتم الربط على هذه المساحة من الملف b.
مثال 4:
// file a.cc extern int a = 10; // file b.cc extern int a;
نفس المثال السابق و الفرق انك وضعت extern بشكل صريح.
مثال 5:
// file a.cc extern int a = 10; // file b.cc extern int a = 5;
نفس المثال الأول لأنه المترجم يقوم بوضع قيمة افتراضية للمتغيرات و الفرق انك بدلا من ان تتركه يضع صفر للمتغير المعرف بالملف b قمت انت بوضع قيمة خاصة بك و ايضا قمت بإستخدام extern بشكل صريح و حيث ان كلاهما متغير عام فلا تأثير لها لأنها الإفتراضية.
اقتباسالقسم الثاني : ما معنى العبارة في الكود السابق ؟
تعني ببساطة ان المترجم سيتخدم الـ name decoration الخاص بلغة السي مع ما يلي extern C و السبب ان لديك مكتبة قديمه تريد التعامل معها او انك تكتب دوال ستستخدمها فيما بعد مع مترجم لغة السي.
و الله ولي التوفيق
مدونتي: C++ Tips and Tricks
شكرا أخي Sn@cker
رابط مفيد بالفعل وإجابات ممتازة ... ولكن ألا ينقص الأمثلة في الكود عبارة include
في بداية الملف الثاني دوماً ...أو أي تنبيه لعملية الربط بين الملفين ؟
______________________________________
شكرا أستاذ @C++er ...
إذا يمكننا تلخيص extern بأنها تصريح عن وجود تعريف واحد للمتحول بمكان ما والمسؤول عن التأكد من وجودها
هو عملية الربط LINK وليست عملية الترجمة Compile
وفقا لما ذكرته : عندي سؤال ... إذا كانت الحالة الافتارضية للمتحولات هي extern
اقتباسالوضع الإفتراضي للمتغيرات العامه داخل الـ translation unit يكون external linkage و هذا يعنى أن المتغيرات العامه كلها تكون بـ extern سواء كتبت او لا.
لم أقتنع بهذه العبارة ..فهذا يعني أن وجودها مثل عدمها ..
أي أن المثالين الخاطئ والصحيح ...متطابقان؟!!
//المثال الخاطئ // file a.cc int a = 10; // file b.cc int a;
إذا كانت الحالة الافتراضية لهما extern فهل المثال التالي يطابق الأول؟
//المثال الصحيح // file a.cc extern int a = 10; // file b.cc extern int a;
وبالتالي أرى أن الحالة الافتراضية ليست extern فما ردك ؟
المثال التي من أفضل الأمثلة التي رأيتها
حيث يتم استخدام المتحولات قبل تعريفها
#include <stdio.h>
int main(void)
{
extern int first, last; /* use global vars */
printf("%d %d", first, last);
return 0;
}
/* global definition of first and last */
int first = 10, last = 20;من الامور الهامة الواجب ذكرها في موضوع الextern ... مال الفرق بينها وبين أي متحول آخر في ملف آخر
ونقوم بعمل include بين الملف الأول والملف الثاني !!
الفرق هو أنه لدينا خياران في حالة extern فإما أن نقوم بعمل include للملف الذي يحوي تعاريف المتحولات ...
أو أن نقوم بعمل LINK لهذا الملف بحيث يكون مرتبط (روحياً) بالملف الذي استدعاه
أرجو التصحيح إن كنت أخطأت فيما قلت ...
والسلام عليكم ورحمة الله
هناك فرق بين extern و extern "C" الأولى عامة وموجودة في C و C++ والأخرى خاصة بـC++ ولاتوفرها C.
تدعم C++ خاصية زيادة تحميل الدوال function overloading بمعنى أنّه يمكن لعدة دوال أن تتشارك في نفس الإسم، كي تتمكن مصرفات الـC++ من عمل هذا فإنها تعدّل في أسماء الدوال لتنتج دوال مختلفة. مثلاً لديك هذا البرنامج:
int my_function(int p);
char my_function(char p);
long my_function(long p);
int main(int argc, char **argv)
{
my_function(1); /* 'int my_function(int p)' ستستدعي */
my_function('a'); /* 'char my_function(char p)' ستستدعي */
my_function(1L); /* 'long my_function(long p)' ستستدعي */
return 0;
}
int my_function(int p)
{
return p;
}
char my_function(char p)
{
return p;
}
long my_function(long p)
{
return p;
}عند تصريف البرنامج تحت gcc سيولد:
(gdb) disassemble main Dump of assembler code for function main: 0x08048474 <main+0>: push %ebp 0x08048475 <main+1>: mov %esp,%ebp 0x08048477 <main+3>: and $0xfffffff0,%esp 0x0804847a <main+6>: sub $0x10,%esp 0x0804847d <main+9>: movl $0x1,(%esp) 0x08048484 <main+16>: call 0x80484a8 <_Z11my_functioni> ; لاحظ أنّ أسماء الدوال إختلفت 0x08048489 <main+21>: movl $0x61,(%esp) 0x08048490 <main+28>: call 0x80484b0 <_Z11my_functionc> 0x08048495 <main+33>: movl $0x1,(%esp) 0x0804849c <main+40>: call 0x80484c2 <_Z11my_functionl> 0x080484a1 <main+45>: mov $0x0,%eax 0x080484a6 <main+50>: leave 0x080484a7 <main+51>: ret End of assembler dump. (gdb)
كما ترى فمصرف الـC++ يقوم بتعديل أسماء تلك الدوال في كامل البرنامج. الكلمة extern "C" تعطل هذا التصرف حيث تحافظ الدالة على إسمها:
extern "C" int my_function(int p); /* ألغينا التلاعب بإسم الدالة عنها */
char my_function(char p);
long my_function(long p);
int main(int argc, char **argv)
{
my_function(1); /* 'int my_function(int p)' ستستدعي */
my_function('a'); /* 'char my_function(char p)' ستستدعي */
my_function(1L); /* 'long my_function(long p)' ستستدعي */
return 0;
}
int my_function(int p)
{
return p;
}
char my_function(char p)
{
return p;
}
long my_function(long p)
{
return p;
}البرنامج الناتج:
(gdb) disassemble main Dump of assembler code for function main: 0x08048474 <main+0>: push %ebp 0x08048475 <main+1>: mov %esp,%ebp 0x08048477 <main+3>: and $0xfffffff0,%esp 0x0804847a <main+6>: sub $0x10,%esp 0x0804847d <main+9>: movl $0x1,(%esp) 0x08048484 <main+16>: call 0x80484a8 <my_function> ; ستحافظ الدالة على إسمها 0x08048489 <main+21>: movl $0x61,(%esp) 0x08048490 <main+28>: call 0x80484b0 <_Z11my_functionc> 0x08048495 <main+33>: movl $0x1,(%esp) 0x0804849c <main+40>: call 0x80484c2 <_Z11my_functionl> 0x080484a1 <main+45>: mov $0x0,%eax 0x080484a6 <main+50>: leave 0x080484a7 <main+51>: ret End of assembler dump. (gdb) q
لاحظ أنه لن يُمكنك إستخدام extern "C" مع أكثر من دالة وسعطيك المصرف خطأ بأنه لايمكن لدالتين مخلفتين أن تتشاركان نفس الإسم (كما قلت أن المصرف يضطر أن يتلاعب بالإسم كي يتمكن من زيادة تحميل الدالة).
يمكن إستخدام extern "C" بطريقتين:
extern "C" void function1(void);
// أو
extern "C" {
void function2(void);
};لكن الطريقة الأفضل لإستخدامها:
#ifdef __cplusplus /* أولاً تحقق من أن هذا المصرف مصرف C++ */
extern "C" { /* إذا كان كذلك فستفعل extern "C" { */
#endif
void function(void); /* ضع الدوال التي تريدها */
#ifdef __cplusplus /* إختم جملة extern "C" { */
};
#endifغالباً لن تحتاج لإستخدام extern "C" في البرامج العادية. لكن كثيراً ماتستخدم عند بناء المكتبات.
في ويندوز يمكن وضع مجموعة دوال في مكتبة تتشارك دوالها عدة عمليات وذلك للحفاظ على الذاكرة. كي تعمل هذا يُمكنك إستخدام التوجيه __declspec(dllexport) مع الدوال التي تريد تصديرها وحينها يُمكنك مثلاً تحميل مكتبة الـdll هذه بإستخدام LoadLibrary والحصول على عنوان الدالة بإستخدام GetProccAddress أو الربط الدينامكي معها.
لكن هناك مشكلة تسببها مصرفات الـC++، لو صرفت هذا البرنامج كبرنامج C++ مع gcc :
__declspec(dllexport) int my_function(int p);
__declspec(dllexport) int my_function(int p)
{
return p;
}وبنيته كـdll:
>g++ mylib.cpp -shared -o mylib.dll
وحاولت تحميل الدالة my_function منها فلن تستطيع:
#include <windows.h>
#include <stdio.h>
int main(int argc, char **argv)
{
HMODULE hMyLib;
int (*my_function)(int);
hMyLib = LoadLibrary(TEXT("mylib.dll"));
if( hMyLib == NULL ) {
printf("Error: Cannot load mylib.dll\n");
return -1;
}
my_function = (int (*)(int)) GetProcAddress(hMyLib, "my_function");
if( my_function == NULL ) {
printf("Error: Cannot find the address of 'my_function'\n");
FreeLibrary(hMyLib);
return -1;
}
my_function(1);
FreeLibrary(hMyLib);
return 0;
}>gcc loader.c -o loader >loader.exe Error: Cannot find the address of 'my_function' >
سبب ذلك أنّ إسم الدالة my_functuon سيصدر كـZ11my_functioni:
وهذا ليس مانريده. حل هذه المشكلة يكون بإستخدام extern "C":
#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) int my_function(int p);
#ifdef __cplusplus
};
#endif
__declspec(dllexport) int my_function(int p)
{
return p;
}>g++ mylib.cpp -shared -o mylib.dll >loader.exe > /* لايوجد خطأ */
على لينكس وأنظمة يونكس عموماً إذا أردت بناء shared object ، يشبه الـdll في ويندوز، فيلزمك أن تستخدم extern "C" كي تستطيع تحميل تلك المكتبة لبرنامجك. مثلاً هذا البرنامج :
#include <stdio.h>
int my_function(int p);
int my_function(int p)
{
return p * 2;
}لو بنيته كـshared object بمصرف C++ وحاولت تحميله بإستخدام dlsym:
#include <stdio.h>
#include <dlfcn.h>
int main(int argc, char **argv)
{
void *objh;
int (*my_function)(int);
objh = dlopen("./libfoo.so", RTLD_LAZY);
if(! objh ) {
fprintf(stderr, "Error: %s\n", dlerror());
return -1;
}
if(! (my_function = dlsym(objh, "my_function")) ) {
fprintf(stderr, "Error: %s\n", dlerror());
dlclose(objh);
return -1;
}
printf("%d\n", my_function(2));
dlclose(objh);
return 0;
}فسيعطيني خطأ أنه لايمكنه إيجاد الدالة my_function:
$ g++ -shared example.cpp -o libfoo.so $ gcc loader.c -o loader -ldl $ ./loader Error: ./libfoo.so: undefined symbol: my_function $
لحل هذه المشكلة يلزم تصديره مع extern "C" أولاً:
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
int my_function(int p);
#ifdef __cplusplus
};
#endif
int my_function(int p)
{
return p * 2;
}$ g++ -shared example.cpp -o libfoo.so $ ./loader 4 $
تم تعديل هذه المشاركة بواسطة Mr.B في 19 ديسمبر 2012 في 15:16
شكرا لك أخي MR.B أجبت عن سؤالي الثاني بشكل كامل ...
اقتباس#ifdef __cplusplus /* أولاً تحقق من أن هذا المصرف مصرف C++ */ extern "C" { /* إذا كان كذلك فستفعل extern "C" { */ #endif void function(void); /* ضع الدوال التي تريدها */ #ifdef __cplusplus /* إختم جملة extern "C" { */ }; #endif
فهمتها تماماً ...أي أنك تستدعي طريقة التصريف الخاصة بلغة C للأشياء المعرفة ضمن القوسين ...
وهناك حسب ما قرأت يمكن أن تكتب أي لغة لو كان المترجم يدعمها ...
مثلاً "extern "Fortran أو ما شابه ذلك ... (لو افترضنا ان المترجم يعرف آلية تصريفها) ...
إذا فهي أمر للمصرف باستخدام إعدادات خاصة لا أكثر ...وبالتالي لا معنى لدراستها دون معرفة بالمصرفات والفروق بينها ...
جزاك الله خيرا
اقتباسإذا كانت الحالة الافتراضية لهما extern فهل المثال التالي يطابق الأول؟
لا و السبب ذكرته فى المشاركه:
اقتباسجميع المتغيرات العامه يتم حجز لها مساحة سواء قمت بوضع قيمة مبدئية لها او لم تضعهم و بالتالي إذا كان لديك ملفان translation units و بهم متغيران عامان بنفس الإسم ستنجح عملية الترجمه و لكن الـ linker سيفشل لوجود متغيرين بنفس الإسم و بالتالي إحداهما لابد من ان يتم الإعلان عنه بـ extern و ان يكون تصريح فقط أى لا تضع به قيمة لكي تنجح العملية.
عندما تقوم بالتصريح عن متغير مع وجود extern حينها يعلم المترجم ان هذا المتغير تم تعريفه بملف أخر و يتم إستخدامه هنا اما لو وضعت له قيمه فإن المترجم يعرف ان هذا المتغير تم تعريفه هنا و يمكن الربط عليه من ملف اخر.
و الله ولي التوفيق
مدونتي: C++ Tips and Tricks
أسجل إعجابي لبراعة Mr.B ... يشي فهمك وجهدك أنك ستتفوق وستظهر بقوة. أشد على يدك +1 ... على فكرة تذكرني بالعضو JAAS. ![]()
+1 أيضا لرد C++er الجميل.
" إن الله كتب الإحسان على كل شيء"
::
الإرادة ... تحقق السيادة.