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

عنوان مصفوفة int وعنوان مصفوفة char ، لماذا يختلف الناتج ؟

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

السلام عليكم ،

كنت أرد على سؤال لأحد الأخوة هنا :

/index.php?showtopic=220720&view=getnewpost

ولكن هناك شيء غريب ، ولا أعرف لماذا يتصرّف المترجم هكذا .

لو أردت طباعة عنوان أول عنصر في المصفوفة فسأقوم بكتابة :

int x[]={1,2};
cout<<x<<endl;
cout<<&x[0]<<endl;

النتيجة واحدة . ولكن عند تغيير نوع المصفوفة إلى char فسيطبع النص الكامل في هذه المصفوفة ،

char c[]={'T','H','E'};
cout<<c<<endl;
cout<<&c[0]<<endl;

في كلا الحالتين سيطبع النص كامل THE ، لماذا هذه المعاملة الخاصة لـ char ؟

وهناك سؤال آخر، في نفس السياق ، وهو كيف يمكن أن نفسر السطر الثالث من هذا الكود :

int c[25]={1,2,3};
cout<<c<<endl;
cout<<&c<<endl; // what's wrong with this line ?
cout<<&c[0]<<endl;

كل المخرجات لها نفس الناتج ؟

السطر الثاني و الأخير ، أعرف لماذا ، ولكن السطر الثالث ، أعتقد أنه من المفترض أن يطبع " عنوان المؤشر الذي يؤشر على أول عنصر في المصفوفة " ، أي أنه عنوان مختلف ؟ أليس كذلك ؟

والدليل أنه عند كتابة شيء كهذا :

int *c = new int[3];
c[0] = 1;
c[1] = 2;
c[2] = 3;
cout<<c<<endl;
cout<<&c<<endl; 
cout<<&c[0]<<endl;

فإن الناتج يظهر بشكل متوقع وهو أن عنوان المؤشر مختلف عن عنوان أول عنصر يشير إليه المؤشر .

المترجم المستخدم هو Visual C++ 2010 .

1

logo1.png تطبيق طمأنينة ، نسخة بيتا على أندرويد

عبدالله الشمّري - Al-Shammari

CodingAlone.com

twitter @abshammeri

abshammeri AT gmail.com

github : abshammeri

#2

ولو تلاحظ اخي الشمري .. "

ان لو كتبنا هالسطر بـ المثال حقك الـ char

cout<<&c[1]<<endl;

راح يحذف الحرف الاول وهو الـ T وبدا من الـ الثاني الى الثالث .. ولاتنسى الرموز الي تطلع .. !! مع انها ثلاث حروف .."

ولكن ع حسب تجربتي .. مع اني مبتدئ .. لاتاخذ كلامي ع مبنى الخبره ..

حنا نعرف ان الـ char تاخذ 2 بايت ..

ولو تجرب وتكتب .. !!

#include<iostream>
using namespace std;
void main()
{
char c[]={'T','H','E'};
cout<<c<<endl;
cout<<&c[0]<<endl;
cout<<&c[1]<<endl;
cout<<&c[2]<<endl;
cout<<&c[3]<<endl;
cout<<&c[4]<<endl;
cout<<&c[5]<<endl;
cout<<&c[6]<<endl;
cout<<&c[7]<<endl;

}

شوف رقم 7 ماظهر شي .. ؟ لانه فارررغ ..

انت كاتب 3 حروف يعني 6 خانات .. هذا استنتاجي .. بس ليه الحروف الزائده ..

بس دام انت تستخدم مصفوفه اي ان الخانات بجنب بعض متسلسلــه .. فـ انت تحتاج الى اول عنصر بالمصفوفه ..

ولمعرفه هذا الموقع ..

اكتب ..

cout<<&c<<endl;

طبعا C مصفوفه من نوع char ..

وان شاءالله اكون افدتك

< يستخدم VS6

بس اخوي الشمري .. كيف احصل ع فيجوال بيسك 2010 .. كامــل ..!! مثل الـ 2008 احصـله بالاسواق .. حل احصل الـ 2010 بالاسواق .. ؟

تم تعديل هذه المشاركة بواسطة mr.4one في 9 مايو 2010 في 02:15

2
#3

مواضيع المؤشرات و المصفوفات هى اكثر ما احبه فى هذه اللغه :evil:

اقتباس
في كلا الحالتين سيطبع النص كامل THE ، لماذا هذه المعاملة الخاصة لـ char ؟

هذه ليست معامله خاصه و انما هى طبيعة الـ overload الذى يتم استدعائه من للمعامل >> المعرف للفئه ostream و حيث انك تمرر له char* فبالتالى سيتوقع منك ان ما تمرره له هو نص و ليس حرف و بالتبعيه إذا كنت تمرر نص فلابد ان ينتهى بـ null terminator و حيث انك لم تضع null terminator كأخر حرف فى المصفوفه لذا تم طباعة النص يليه عدة رموز غريبه حتى وصل إلى الـ null terminator بالصدفه.

النسخه التى يتم استدعائها تعريفها هو

ostream& operator<< (ostream& out, const char* s );

إذا اردت ان لا يظهر الحروف الغريبه قم بجعل المصفوفه بالشكل التالى:

char c[]={'T','H','E', '\0'};
cout<<c<<endl;
cout<<&c[0]<<endl;

سبب ظهور النص فى المرتين هو انك فى كل الاحوال تمرر مؤشر لأول حرف فى نص (هذه اجابة مختصره جدا للسؤال الثانى الذى اجابته هى التاليه).

*************************************

اقتباس
هو كيف يمكن أن نفسر السطر الثالث من هذا الكود :

لاحظ المصفوفه التى قمت بتعريفها تم عملها داخل الـ stack , و بالتالى إذا ركزت قليلا ستجد ان القيم التاليه كلها لابد و أن تكون متساويه:

c == &c == &c[0]

كل ما سبق عندما يتم تعريف المصفوفه داخل الـ stack لابد ان يتساووا و إليك السبب:

المتغير c هو مؤشر للعنصر الأول بالمصفوفه لذا

c == &c[0]

هذه هى النقطه السهله. يوجد اكثر من تفسير لقيمة &c سأذكر لك منهم 2:

1 - حيث ان c تشير لموقع العنصر الأول بشكل افتراضى و حيث ان موقع c هو نفسه مكان قيمة c بعد الـ dereference (لإن مكان c داخل الـ stack) لذا فإن:

&c = &c[0]

2 - للحصول على قيمة العنصر الأول يمكنك عمل dereference للمتغير c، بعد الحصول على القيمه قم بإحضار عنوانها و هى ستكون بالشكل التالى

&*c

الكود السابق يمكن إعادة اختصاره إلى الشكل التالى:

&c

و ذلك لأن موقع قيمة العنصر الأول هى نفسها موقع العنصر الأول.

لاحظ ان هذا الكلام ينطبق فقط على المصفوفات عندما تكون داخل الـ stack

ارجو ان يكون الشرح وافى.

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

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

5

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

#4

السلام عليكم ...

بالنسبة للسؤال الأول المتعلق بالـ overloading الخاص بالـ C-String في مكتبة Cpp القياسية فهذا كفى و وفى فيه الأخ محمد, و كما قال مؤشرات الأحرف حالة خاصة, و باقي أنواع المؤشرات تعامل عن طريق النسخة العامة التي تقبل *void

اقتباس
السطر الثاني و الأخير ، أعرف لماذا ، ولكن السطر الثالث ، أعتقد أنه من المفترض أن يطبع " عنوان المؤشر الذي يؤشر على أول عنصر في المصفوفة " ، أي أنه عنوان مختلف ؟ أليس كذلك ؟

صدقني أني لا ألوم أي أحد يكره C و من بعدها ++C بسبب هذا الموضوع بالذات, حتى Stroustrup قال بأنه شر لابد منه لضمان التوافقية مع C.

أولاً, لابد أن تعلم و أظنك تعلم, أن اسم المصفوفة هو عنوان أول عنصر فيها. بكل بساطة خطأ, العبارة السابقة خطأ 100% إلا إذا عدلناها قليلاً لتصبح: اسم المصفوفة هو عنوان أول عنصر فيها إذا دعت الحاجة إلى ذلك أي إذا كان هناك conversion مقبول. كيف؟

بداية دعنا ننظر إلى أبسط مثال, و هو المثال التالي:

char arr[] = {3, 2, 1, 0};

الآن, عندما يرد الاسم arr و الذي يعتبر من نوع مصوفة حرفية رباعية العناصر في مكان يستقبل مؤشر من نوع:

char* p;

فإن قيمة اسم المصفوفة تعامل كما لو كانت العبارة التالية:

char* p = &arr[0];
char* p = arr; // same!

هكذا قرر مصمم C!

الآن, الأمر الذي قلت بأنه خاطئ في البداية يتضح عندما ننظر إلى السطر التالي:

??????? p = &arr;

ماهو نوع p؟

في هذه الحالة, اسم المصفوفة لم يطلب المبرمج معاملته على أنه مؤشر حرفي, و إنما أخذنا عنوان المصفوفة نفسها. و نوع عنوان المصفوفة التي تكلمنا عنها في البداية هو:

char (*p) [4] = &arr;

ببساطة هو مؤشر لمصفوفة حرفية مكونة من أربع حروف, و كما قلنا اسم المصفوفة في هذه الحالة لم تنطبق عليه عملية التحويل.

و لهذا, فإنك عندما تقوم بطباعة عنوان المصفوفة نفسها:

cout<<&c<<endl;

فإن النوع الذي يمكن التحويل إليه هو *void فقط, لأنه يمكن تحويل جميع أنواع المؤشرات إليه, يمكن أن تقول عنه the hole that makes pointers in C++ not type-safe :)

بالطبع ليس هناك implicit conversion للتحويل بين عنوان المصفوفة و بين مؤشر حرفي.

هذا كاف لحل اللغز الذي تتكلم عنه :cool:

ربما خطر ببالك سؤال آخر, و ربما هذا المثال يلقي الضوء عليه:

char array[] = {0, 1, 2, 3, 4};
void* array_address = &array;
void* char_address  = array;
assert(array_address == char_address); // the address is the same!

تلك المؤشرات تشير إلى نفس العنوان بالضبط!

كما تعلم المؤشرات تشير إلى أماكن في الذاكرة, و عندما نقوم بعمل dereferencing للمؤشر فإننا نحصل على تلك القطعة بشكل مختلف حسب نوع المؤشر. فمثلاً:

int some_garbage = 0xdeedbeef;
char* cp         = reinterpret_cast<char*>(&some_garbage);
int*  ip         = &some_garbage;
int sum = 0;
sum += *ip; // *p is the first four bytes shaping an int
sum += *ic; // *ic is the first byte of some_garbage

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

/* 'a++' moves 'a' by one byte in memory. */ 
char* a;
/* 'b++' moves 'b' by on "array of four chars in memory */
char (*b)[4];

كما تلاحظ المؤشر b يتحرك أربع bytes عند زيادة المؤشر, لأن حجم النوع الذي يشير إليه هو 4 bytes مكونة من أربع حروف في مصفوفة. بينما a يتحرك byte واحد فقط.

تحياتي...

تم تعديل هذه المشاركة بواسطة Khaled.Alshaya في 9 مايو 2010 في 14:27

3
#5

@خالد

اقتباس
اسم المصفوفة هو عنوان أول عنصر فيها إذا دعت الحاجة إلى ذلك أي إذا كان هناك conversion مقبول

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

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

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

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

#6

أخ محمد,

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

هل يمكن أن توضح أكثر؟ بمثال من الأفضل, لأني لم افهم الفرق.

اقتباس

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

أقصد بالتحويل, تحويل اسم المصفوفة إلى مؤشر من نوع كذا, و هناك عدة أنواع من الـ Implicit Conversion بين أنواع المؤشرات نفسها. لم أتكلم عن تحويل مؤشر من نوع *void إلى مؤشر من نوع آخر, هل يمكن أن توضح أكثر.

تحياتي...

تم تعديل هذه المشاركة بواسطة Khaled.Alshaya في 9 مايو 2010 في 16:57

#7

اعتقد نقص فى قدرة الoverload حيث لا يمكنها توقعها هل تريد طباعة null-terminated string او pointer كما ذكر الاخ محمد

printf تربح فى تلك الحالة

        char x[]={'T','H','E'};
        printf("x :%p \n",x);
        printf("x : %p \n",&x[0]);

عموما بcast تعمل بشكل صحيح

        char c[]={'T','H','E'};
        cout<<(void*)c<<endl;
        cout<<(void*)&c[0]<<endl;

تم تعديل هذه المشاركة بواسطة apex في 9 مايو 2010 في 16:38

1

name : mohamedyosry

#8
اقتباس
اعتقد نقص فى قدرة الoverload حيث لا يمكنها توقعها هل تريد طباعة null-terminated string او pointer كما ذكر الاخ محمد

لا أعتقد أنه عيب في IO streams, بل على العكس أراه عيباً في تصميم printf. الـ streams قابلة للتطور, بينما printf من غير الممكن تطويرها. السؤال, هو كم مكان وجدت فيه شخص يطبع عنوان مصفوفة حرفية, مقارنة بمن يريدون طباعة النص نفسه؟

لاحظ, أن عملية الـ overloading مفيدة لو نظرت من زاوية أكبر للموضوع. في ++C نوعين للمحارف, و هناك ثلاثة أنواع جديدة ستضاف في Cpp0x للتعامل مع Unicode بجميع الـ Encoding المعروفة utf8 و utf16 و utf32. في حالة الـ streams ستعاملها كأنها نص عادي للطباعة, و بالطبع من الأفضل بكل تأكيد استخدام الـ string classes الملحقة بالمكتبة القياسية!

بينما في حالة printf, فإنك ستضيف رموز جديدة في الـ formatting string. بدلاً من s سيصبح لديك مثلاً s8 و s16 و s32.. و هكذا. بالنسبة للـ streams يمكن أن تجعل كائناتك تعمل كأنها متغيرات عادية:

person p;
cout << p;

هذا أيضاً غير ممكن في حالة printf.

في النهاية أنا مع الرأي القائل بأن printf تعتبر more elegant من الـ streams في حالة عمل formatting معقد. رغم أن الـ streams يمكن تخصيصها بشكل رهيب في حالة كون المطلوب أكثر من عملية formatting كعمل localization و نحوه. لذلك وجدت boost::format التي تأخذ الشكل من printf و القوة من الـ streams :)

تحياتي...

1
#9

انا لم اقلل من قدرات الoverloading بل هي خاصية رائعة وممتازة فى الكثير من الحالات .

انا فقط اردت ان اوضح ان ربما لخاصية ممتازة مثلها ان يكون لها بعض ال drawback البسيطة مثلما اوضحت انت قبل ذلك اثر اخر للfunction overloading يوثر فى زيادة الاوامر الناتجة من الترجمة .

وفى كلتا الحالتين قد تكون متطلباتك تقزم تلك الاثار بالنسبة لما تقدمة لك الامكانية

اقتباس
أن عملية الـ overloading مفيدة لو نظرت من زاوية أكبر للموضوع. في ++C نوعين للمحارف, و هناك ثلاثة أنواع جديدة ستضاف في Cpp0x للتعامل مع Unicode بجميع الـ Encoding المعروفة utf8 و utf16 و utf32. في حالة الـ streams ستعاملها كأنها نص عادي للطباعة, و بالطبع من الأفضل بكل تأكيد استخدام الـ string classes الملحقة بالمكتبة القياسية!

عندما ابرمج بالبايثون او اى لغة عالية المستوى افضل تلك الخصائص . لكن عندما ابرمج بالسى(وربما تتنقل معى تلك العادة الى سى++) اشعر بان الاسلوب القديم مثل printf يعتبر self-describing وان الاشياء الاخرى قد تكون خطيرة وتسبب وقت اطول واصعب فى حالة ظهور bugs (مثلما حدث مع الاخ الشمرى عندما لم يتوقع معاملة خاصة من cout عند اختلاف النوع الذى يرمز الية الموشر ).

وبالتاكيد سيختلف درجة قلقى حيال معالجة الstring بمستوى تعمق برنامجى بتلك التفاصيل .

اقتباس
في النهاية أنا مع الرأي القائل بأن printf تعتبر more elegant من الـ streams في حالة عمل formatting معقد. رغم أن الـ streams يمكن تخصيصها بشكل رهيب في حالة كون المطلوب أكثر من عملية formatting كعمل localization و نحوه. لذلك وجدت boost::format التي تأخذ الشكل من printf و القوة من الـ streams

مكتبة رائعة بقراء بسيطة عنها لاحظت انها توفر اكثر من مستوى للتحكم فى نفس الوقت و مستويات تعقيد مختلفة (تزيد كلما زاد التحكم)

تم تعديل هذه المشاركة بواسطة apex في 9 مايو 2010 في 17:39

name : mohamedyosry

#10

بارك الله فيكم ، واضح الان ..

اقتباس
بس اخوي الشمري .. كيف احصل ع فيجوال بيسك 2010 .. كامــل ..!! مثل الـ 2008 احصـله بالاسواق .. حل احصل الـ 2010 بالاسواق .. ؟

حمّل النسخة Express من موقع Microsoft بشكل مجاني :

http://www.microsoft.com/express/downloads/

logo1.png تطبيق طمأنينة ، نسخة بيتا على أندرويد

عبدالله الشمّري - Al-Shammari

CodingAlone.com

twitter @abshammeri

abshammeri AT gmail.com

github : abshammeri

#11

أخى خالد:

اقتباس
إذا كانت المصفوفه تم حجزها داخل الـ heap فهذه المقوله صحيحه حيث ان اسم المصفوفه حينها سيشير لموقع اول عنصر بالمصفوفه و ليس قيمة اول عنصر بها.

هذا مثال على هذه المقوله:

int* heap_arr = new int[3];

for (int i = 0; i < 3; i++)
    heap_arr = i;

cout << "array location: " << hex << &heap_arr << endl;

cout << "1st element location: " << hex << &heap_arr[0] << endl;

اقتباس
اما إذا تم تعريف المصفوفه داخل الـ stack فموقع اول عنصر بالمصفوفه يتطابق مع اسم المصفوفه.

و هذا مثال على هذه المقوله:

	int stack_arr[] = {0, 1, 2};

	cout << "array location: " << hex << &stack_arr << endl;

	cout << "1st element location: " << hex << &stack_arr[0] << endl;

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

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

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

عدد الزوار حالياً

المتواجدون خلال آخر دقيقتين · يتحدّث كل ٣٠ ثانية

—الإجمالي—أعضاء مسجّلون—زوار بدون تسجيل

جارٍ التحقق من المتواجدين…