الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

المصفوفات و عناويين الذاكرة

بدأه yahya91 في 29 مارس 2010 · 10 رد · 1,832 مشاهدة · في لغة C و ++C
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

السلام عليكم

لقد أخبرنا الدكتور أن المصفوفات تساعد في اختصار زمن تنفيذ البرنامج عند تنفيذه لأن المصفوفة تأخذ كتلة من الذاكرة واحدة و تضع فيها المتغيرات فلو كانت المصفوفة ثلاثية الحجم

int arr[3];

و لدينا ثلاث متغيرات

int var1,var2,var3;

فإن المصفوفة تخزن عناصرها في عناوين متتالية أما التعريف العشوائي للمتغيرات الثلاث لن يجز بالضرورة أماكن متتالية .

و بالفعل صدقت هذا الكلام و لكن عندما جرّبتُ أن أطبق تقنية الـpointer للتأكد من صحة ذلك ظهرت معي النواتج التالية و هي أنه قد حجز للمتغيرات الثلاث أماكن متتالية من الذاكرة كما في المصفوفة فما التفسير

برنامج يستخدم متغيرات ثلاثة ليست ضمن مصفوفة :

#include<iostream.h>
void main()
{
	int var1,var2,var3;
	cout<<"enter var 1"<<endl;
cin>>var1;
	cout<<"enter var 2"<<endl;
cin>>var1;
	cout<<"enter var 3"<<endl;
cin>>var1;
cout<<"the places of this variables in memory are"<<&var1<<","<<&var2<<","<<&var3<<","<<endl;
}

الخرج التنفيذي:

ظهرت عناوين الذاكرة كالتالي:

0012ff7c,0012ff78,0012ff74

كود برنامج يستخدم مصفوفة ثلاثية الحجم:

#include<iostream.h>
void main()
{
	int var[3];
	cout<<"enter var 1"<<endl;
cin>>var[0];
	cout<<"enter var 2"<<endl;
cin>>var[1];
	cout<<"enter var 3"<<endl;
cin>>var[2];
cout<<"the places of this variables in memory are"<<&var[0]<<","<<&var[1]<<","<<&var[2]<<","<<endl;
}

الخرج التنفيذي:

ظهرت عناوين الذاكرة كالتالي:

0012ff74,0012ff78,0012ff7c

أي فقط انعكسوا..

#2

من البديهي انها ستظهر لديك النتائج بهذه الطريقة لانك قمت بحجزها متتالية كما انك لم تحجز متغيرات من انواع اخرى بينها . فهنا الحجز يتم حسب أولوية التنفيذ

. ولكن لما لاتجرب هذه وترى الفرق ؟؟

#include<iostream.h>
void main()
{
		  int var1;
		  char c;
		  int var2,var3;
		  cout<<"enter var 1"<<endl;
cin>>var1;
		  cout<<"enter var 2"<<endl;
cin>>var1;
		  cout<<"enter var 3"<<endl;
cin>>var1;
cout<<"the places of this variables in memory are"<<&var1<<","<<&var2<<","<<&var3<<","<<endl;
}

تحياتي

OMANI FOR EVER

-------------------------------------------------

post-12787-12780191801974.jpg

-------------------------------------------------

#3

كلام سليم,

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

بينما في حالة المتغيرات المحلية, فرغم أنها تكون متتالية في تعريفها داخل دالة مثلاً, لا يشترط على صانع المترجم أن يضع تلك المتغيرات في أماكن متتالية في الذاكرة,

في الحقيقة أن اللغة لا تشترط عليه شيء.

الآن, هل هذا يعني أن المتغيرات يجب ألا تكون متتالية في الذاكرة؟!

بالطبع لا, لأننا نقول أن اللغة لا تشترط و هذا يعني أن المترجم حر بترتيبهم حسبما يريد. و لكن في غالب الأحيان فإنه يتم وضعهم في أماكن متتالية في الذاكرة.

هناك أحيان يقوم فيها المترجم بإعادة ترتيب المتغيرات, و ذلك يتعلق بعملية الـ Padding لتسريع عملية الوصول إلى المتغيرات في الذاكرة, و لكن هذا Implementation Detail لا أكثر.

عموماً, لا تشغل بالك بأيهم أسرع لأنه لا يوجد شيء أسرع من شيء, و إنما هناك حالة أسرع من حالة, و نحن نتكلم عن جزء من المليون في الثانية حتى لو وضعنا حاسوباً يراقب الموضوع فلن يلاحظه.

لذلك مواضيع الـ performance في الغالب كلها كلام في كلام :)

تحياتي...

تم تعديل هذه المشاركة بواسطة Khaled.Alshaya في 29 مارس 2010 في 23:32

#4

موضوع حجز مساحات الذاكره للمتغيرات يعتمد على ترتيب تعريف المتغيرات داخل الداله.

موضوع ترتيب العناصر داخل الذاكره يعتمد على كيفية حجز الذاكره للمتغيرات داخل الـ stack، حيث ان لكل متغير ايا كان نوعه مكان داخل الـ stack و عملية حجز الذاكره للمتغيرات داخل الـ stack تبدأ من اخر متغير تم تعريفه داخل الداله إلى اول متغير تم تعريفه بها و لذلك سبب و هو ليكون ترتيب المتغيرات تمام كما قمت انت بتعريفه و لأن ادخال البيانات غلى الـ stack يكون عن طريق push لذا يتم ادخال اخر متغير تم تعريفه بالداله اولا يليه ما قابله و هكذا وصولا لأول متغير بالداله ليكون على قمة الـ stack.

عند تعريفك لمصفوفه ذات حجم ثابت داخل الكود مثل التى قمت بتعريفها يتم حجز مساحه لها، هذه المساحه تساوى عدد العناصر * حجم نوع البيانات و حيث ان عملية الحجز تتم على مره واحده فترتيب عناصر المصفوفه يكون بشكل سليم اى العنصر الأول بها يتم حجزه اولا يليه العنصر الثانى ثم الثالث، اما عند تعريفك للمتغيرات فيتم ادخال المتغير الثالث اولا يليه المتغير الثانى يليه المتغير الأول و ذلك حتى يتسنى اخراج المتغير الأول اولا يليه الثانى يليه الثالث.

و الله ولى التوفيق

مدونتي: C++ Tips and Tricks

#5
اقتباس
عموماً, لا تشغل بالك بأيهم أسرع لأنه لا يوجد شيء أسرع من شيء, و إنما هناك حالة أسرع من حالة, و نحن نتكلم عن جزء من المليون في الثانية حتى لو وضعنا حاسوباً يراقب الموضوع فلن يلاحظه.

جزء من المليون في الثانية قد يصنع فارق كبير اثناء تعاملك مع كم ضخم جدا من البيانات (ذات حجم الجيجا) كترتيب او اجراء عمليات حسابيه معقده عليه و بالتالى قم بحساب هذا الجزء الصغير من الثانيه لكل بايت تم تحميله داخل الذاكره ليتم نقله او اجراء عمليات حسابيه عليه و ستجد انه قد يصلك معك فى المجمل إلى 5 او 6 ثوانى إذا كان جهازك له بروسسور ذات core واحد فقط (مثل جهازى).

اجزاء من الثانيه تصنع فارق كبير، و لهذا نحتاج فى بعض الأحيان للمزج ما بين كود الـ cpp مع كود اسمبلى لرفع الأداء و زيادة السرعه.

و الله ولى التوفيق

تم تعديل هذه المشاركة بواسطة Muhammad alaa في 30 مارس 2010 في 00:06

1

مدونتي: C++ Tips and Tricks

#6
اقتباس
موضوع ترتيب العناصر داخل الذاكره يعتمد على كيفية حجز الذاكره للمتغيرات داخل الـ stack، حيث ان لكل متغير ايا كان نوعه مكان داخل الـ stack و عملية حجز الذاكره للمتغيرات داخل الـ stack تبدأ من اخر متغير تم تعريفه داخل الداله إلى اول متغير تم تعريفه بها و لذلك سبب و هو ليكون ترتيب المتغيرات تمام كما قمت انت بتعريفه و لأن ادخال البيانات غلى الـ stack يكون عن طريق push لذا يتم ادخال اخر متغير تم تعريفه بالداله اولا يليه ما قابله و هكذا وصولا لأول متغير بالداله ليكون على قمة الـ stack.

هذا Implementation Detail :)

غالباً, المتغيرات المحلية يتم وضعها في Registers عندما يكون لدينا Optimized Code.

اقتباس
اجزاء من الثانيه تصنع فارق كبير، و لهذا نحتاج فى بعض الأحيان للمزج ما بين كود الـ cpp مع كود اسمبلى لرفع الأداء و زيادة السرعه.

99.99999999999% من البرامج التي نصنعها تكون I/O bounded أو network bounded

أما القلة القليلة من البرامج التي تكون Computationally bounded فهي في الغالب تكون على الـ HPC, و ليست على الأجهزة الشخصية.

في تلك الحالة بالفعل يتم كتابة أجزاء ليست بالهينة بالـ Assembly الخاصة بتلك المنصة.

تحياتي....

#7
اقتباس
كما انك لم تحجز متغيرات من انواع اخرى

شكراً أخي العماني و لكن كنت أقصد المقارنة بين 3 متغيرات معرفة بشكل منفصل دون مصفوفة من نوع integer و مصفوفة ذات جحم ثلاثي من نوع Integer من حيث تخزينهم في الذاكرة .

وبالنسبة لاختصار الوقت باستخدام هذه التقنية فكما قال أخي خالد هي موضوع غير ملحوظ بشكل كبير.

و لكن توجد نقطة لم أفهمها من كلام أخي محمد علاء الدين وهي:

اقتباس
اما عند تعريفك للمتغيرات فيتم ادخال المتغير الثالث اولا يليه المتغير الثانى يليه المتغير الأول و ذلك حتى يتسنى اخراج المتغير الأول اولا يليه الثانى يليه الثالث

و الباقي إن شاء الله فهمته" طبعاً ماعدا نقاش الأخ علاء مع الأخ خالد و الذي فوق مستواي كمبتدئ و الذي أدخلو استخدام لغة الألة بلغة السي بلس بلس :)"

#8
yahya91 كتب:

و لكن توجد نقطة لم أفهمها من كلام أخي محمد علاء الدين وهي:

اقتباس
اما عند تعريفك للمتغيرات فيتم ادخال المتغير الثالث اولا يليه المتغير الثانى يليه المتغير الأول و ذلك حتى يتسنى اخراج المتغير الأول اولا يليه الثانى يليه الثالث

قبل ان ابدء بالشرح تذكر فقط قول الأخ خالد

اقتباس
هذا Implementation Detail

انا اعمل على مترجمات MSVC++ و Intel C++ و كلاهما يعمل بنفس الطريقه المذكوره بالأعلى (الجزء الخاص بحجز المتغيرات داخل الـ stack).

انظر لهذه الداله

void Test()
{
    int i = 10;

    cout << i;

    float f = 3.14;

    cout << f;

    int r = 12;

    cout << r;
}

كيف تعمل الداله السابقه:

اثناء عملية تحويل الكود للنسخه التنفيذيه يتم تجميع بعض المعلومات عن الداله منها المساحه التى تحتاجها الداله داخل الـ stack و هى فى حالتنا هذه 12 بايت للمتغيرات i و f و r على التوالى و كما ترى من ترتيب الكود داخل الداله فإنه يتم حجز مساحة المتغير i اولا يليه f يليه r، و هنا تبدأ المشكله لإن الـ stack بطبيعته يعمل بإسلوب Last In First Out و بالتالى إذا تم حجز مساحة i اولا سيتم بعدها حجز مساحة f و هذا يعنى ان f سيسبق i فى الترتيب داخل الـ stack و بالتالى إذا اردنا ان نحصل على قيمة i لابد من اخراج قيمة f اولا حتى نستطيع الحصول على قيمة i و بالتالى ستضيع قيمة f المخزنه، لذا ما العمل لحل هذه المشكله؟

الحل هو ادخال المتغيرات داخل الـ stack فى عكس اتجاه بمعنى ادخال المتغير r اولا ثم ادخال f ثم ادخال i و بالتالى عندما يبدأ تنفيذ كود الداله و يتم الحصول على القيم الخاصه بالمتغيرات سيكون المتغير i هو المتاح اولا و ذلك لأنه كان اخر من ادخل يليه المتغير f يليه المتغير r.

السؤال الذى قد يتبادر إلى ذهنك الان، ماذا يحدث للمتغيرات التى تم التصريح عنها و لكن لم يتم ادخال لها قيمه و لم يتم استخدامها داخل الداله؟

هذه المتغيرات يقوم المترجم بإعطائك تحذير عنها و هو unreferenced local variable و فى حالة نسخ الـ debug من البرنامج يتم حجز مساحة لها داخل الـ stack و لكن يترك مكانها كما هو بالقيمه التى يحتوى عليها و تسمى القيمه التى يحتوى عليها فى هذا الوقت هى junk اى قيم غير صحيحه، فى النسخ الـ release من البرنامج حيث الـ optimization مفعله يتم حذف هذه المتغيرات.

و الله ولى التوفيق

مدونتي: C++ Tips and Tricks

#9

جزاك الله خيراً أخي علاء ...شرح مفصل و لقد فهمته و لكن لماذا الكومبايلر ألزم نفسه بهذا الترتيب ، بمعنى أليس الكومبايلر يتعامل مع(((((عناوين)))))الأمالكن التي تم حجزها لمتغيرات و بالتالي هو يذهب إلى عنوان الذاكرة الذي باسم المتغير f مثلاً و يدخل أو يخرج القيمة مثلاً دون أن ينظر إلى ما قبله أو مابعده .

و أكرر شكري لك أخي علاء فلقد أحسنت الشرح.

#10
اقتباس
لكن لماذا الكومبايلر ألزم نفسه بهذا الترتيب

تعتمد على الـ Algorithm التى تتبعها الـ run-time environment لإدارة ذاكرة الـ stack.

اقتباس
أليس الكومبايلر يتعامل مع(((((عناوين)))))الأمالكن التي تم حجزها لمتغيرات و بالتالي هو يذهب إلى عنوان الذاكرة الذي باسم المتغير f مثلاً و يدخل أو يخرج القيمة مثلاً دون أن ينظر إلى ما قبله أو مابعده .

انت اوشكت للوصول للحقيقه. حتى تكون الألفاظ دقيقه، المترجم وظيفته تنتهى بعد تحويل الكود المصدرى إلى النسخه التنفيذيه (القابله للتنفيذ عن طريق البروسسور)، عند هذه المرحله لكل مترجم مجموعة مكتبات مساعدة تشكل ما يسمى بـ run-time environment و هى المسئوله عن جعل تنفيذ برنامجك يتم بنفس الصوره التى قمت ببرمجته عليه.

عندما استخدم لفظ مترجم إذا كان على الكود فهذا يعنى المترجم ذاته و إذا كان يتعلق بإدارة الذاكره و الأمور الخاصه بتنفيذ البرنامج فأنا اقصد الـ run-time environment.

بعد تحويل البرنامج للنسخه التنفيذيه يتم الوصول للمتغيرات برقم هذا الرقم له دلاله معينه فمثلا دعنا نتكلم بمثال الخاص بالداله المذكوره سابقا

void Test()
{
    int i = 10;

    cout << i;

    float f = 3.14;

    cout << f;

    int r = 12;

    cout << r;
}

عرفنا من قبل ان مساحة الـ stack التى سيتم حجزها للداله السابقه هى 12 بايت (قم بمراجعة المشاركات السابقه لتعرف لماذا).

ألمتغير i تم تعريفه اولا داخل الداله لذا يصبح ترتيبه صفر و ذلك لأنه الأول و المتغير f هو المتغير الثانى داخل الداله و لكن ترتيبه سيساوى ترتيب المتغير i اضافة إلى مساحته (ستعرف لماذا لاحقا) و حيث ان نوع المتغير i هو int لذا مساحته هى 4 بايت (كما تعاملنا معها فى المثال الموجود بالمشاركه السابقه) و فى هذه الحاله يصبح ترتيب المتغير f يساوى 0 + 4 = 4 و المتغير r ترتيبه سيساوى ترتيب المتغير f بالإضافه إلى مساحة المتغير f و هى 4 (نوع f هو float و يستهلك مساحة 4 بايت من الذاكره) لذا يصبح ترتيب المتغير r هو (0 + 4) + 4 = 8.

عندما يتم استدعاء الداله يتم حجز الـ 12 بايت داخل ذاكرة الـ stack و يتم حفظ عنوان الـ stack الجديد داخل المسجل ESP (اذا لم تعرف ما هو يمكنك اعتباره كمتغير موجود داخل البروسسور و هذا اسمه)، بعد هذا ليتم الوصول للمتغير f يتم اضافة ترتيبه إلى الرقم المسجل داخل ESP و بالتالى الموقع الحقيقى للمتغير f داخل الذاكره يكون ESP+4 و هذا هو اسمه الحقيقى التى يتم التعامل معه.

هذه الأمور يمكنك معرفتها بدراسة لغة الاسمبلى.

و الله ولى التوفيق

مدونتي: C++ Tips and Tricks

#11
اقتباس
المسجل ESP (اذا لم تعرف ما هو يمكنك اعتباره كمتغير موجود داخل البروسسور و هذا اسمه)

لقد أخذنا وظيفته باختصار شديد و هي حسب ما أعلم لها علاقة بقراءة المواقه في الذاكرة الرئيسية و لكن على كل الأحوال لقد وصلت الفكرة بشكلها العام و كما قلت أخي علاء سأفهم ذلك عندما آخذ لغة الأسيمبلي و بنية و تصميم الحاسوب .

شكراً لك على إخلاصك في الشرح

مواضيع مشابهة