المساعدة - البحث - قائمة الأعضاء - التقويم
نسخة كاملة: تصرف غريب في VC6 !
برمجة - شبكات - كمبيوتر - منتديات الفريق العربي للبرمجة > منتديات لغات البرمجة العام > منتدى مبرمجي C و ++C و C++.NET > أرشيف منتدى قسم السي ++
آل عيد

كود
#include <iostream.h>



void main()

{

int A = 2;

cout << (++A + ++A ++A) << endl;

}



يفترض أن يكون الناتج 12 ولكن نلاحظ أن الناتج يكون 13 ، ولقد قمت بتغيير القيم فوجدت دائما أن الناتج يزيد بمقدار واحد فهل يملك أحد تفسيرا لهذا ؟
LizardKing
عزيزي آل عيد،
السلام عليكم ورحمة الله وبركاته:

أولاً أردت أن تكتب البرنامج كما يلي:

[code2]
#include

void main()
{
int A = 2;
cout << (++A + ++A + ++A) << endl;
}
[/code2]

حتى أستطيع توضيح الأمر، سأفرض مثالاً يعطي نفس المفعول السابق ونفس النتيجة، وذلك للتفرقة بين متغيرات A الثلاث الظاهرة في التعبير الرياضي السابق، فيصبح البرنامح كما يلي:

[code2]
#include

void main()
{
int X = 2;
int &Y = X;
int &Z = X;

int B = ++X + ++Y + ++Z;

cout << B << endl;
}
[/code2]

لاحظ أن المتغير الأصلي هو X، أما Y و Z فهما متغيران مرجعيان يشيران إلى نفس المتغير X، أو تستطيع القول أنهما أسماء أخرى له (مبدئياً فقط).

سيحسب المترجم القيم السابقة كما يلي:
القيم الأصلية للمتغيرات هي:
X=2
Y=2
Z=2
سيضيف واحد إلى المتغير X، فتصبح القيم كما يلي:
X=3
Y=3
Z=3
لاحظ أن المتغيرات السابقة تغيرت جميعها إلى 3 وذلك لأنها تشير إلى نفس المكان في الذاكرة.

ثم يضيف واحد إلى المتغير Y، فتصبح القيم كما يلي:
X=4
Y=4
Z=4
لقد تغيرت جميع القيم لنفس السبب السابق.

بعد ذلك يحسب مجموع X + Y والنتيجة هي 8 (أي 4 + 4).

ثم يضيف واحد إلى المتغير Z، فتصبح القيم كما يلي:
X=5
Y=5
Z=5

ثم يحسب مجموع (X + Y) زائد Z والنتيجة هي 13 (أي 8 + 5).


وللتأكد من صحة ما ذكرت أنظر إلى البرنامج الأخير بلغة التجميع:

[code2]

; 7 : int X = 2;

mov DWORD PTR _X$[ebp], 2

; 8 : int &Y = X;

lea eax, DWORD PTR _X$[ebp]
mov DWORD PTR _Y$[ebp], eax

; 9 : int &Z = X;

lea ecx, DWORD PTR _X$[ebp]
mov DWORD PTR _Z$[ebp], ecx

; 10 :
; 11 : int B = ++X + ++Y + ++Z;

; ++X
mov edx, DWORD PTR _X$[ebp]
add edx, 1
mov DWORD PTR _X$[ebp], edx


; ++Y
mov eax, DWORD PTR _Y$[ebp]
mov ecx, DWORD PTR [eax]
add ecx, 1
mov edx, DWORD PTR _Y$[ebp]
mov DWORD PTR [edx], ecx

; X + Y
mov eax, DWORD PTR _Y$[ebp]
mov ecx, DWORD PTR _X$[ebp]
add ecx, DWORD PTR [eax]


; ++Z
mov edx, DWORD PTR _Z$[ebp]
mov eax, DWORD PTR [edx]
add eax, 1
mov edx, DWORD PTR _Z$[ebp]
mov DWORD PTR [edx], eax

mov eax, DWORD PTR _Z$[ebp]
add ecx, DWORD PTR [eax]
mov DWORD PTR _B$[ebp], ecx

[/code2]

بإختصار فإنه يضيف واحد إلى X فتزيد جميع المتغيرات بواحد ثم يضيف واحد إلى Y فتزيد أيضاً جميع المتغيرات بواحد ثم يحسب مجموعها ويضعها في مسجل العداد ecx والذي تصبح قيمته 8

بعد ذلك يضيف واحد إلى Z فتصبح قيم الجميع 5 ثم يضيف قيمة Z (والتي تساوي 5) إلى ecx والذي لا يزال يحمل المجموع القديم (والذي يساوي 8) فتصبح النتيجة 13 ثم يخزن هذه القيمة في المتغير B.

الخطأ هنا ليس خطأ المترجم بقدر ما هو خطأ المبرمج الذي أختار تركيباً عجيباً كهذا regular_smile.gif

هل تريد مفاجئة أخرى ؟ غير إعدادات البناء من Debug إلى Release ثم نفذ البرنامج مرة أخرى، وانظر على ماذا ستحصل !!! regular_smile.gif

إذا لم تستطع تفسير هذا التصرف الأخير أعلمني بذلك....

تحياتي لك وللجميع.
هاني الأتاسي
أشكرك على هذا التفصبل أخي LizardKing ...

وأنا فعلا أستغربت عتدما وجددت الاجابتين مختلفتين بين نسخة ال Debug والRelease .. ولكن أخي أنا على حسب اعتقادي .. إذا دققت كويس سوف تجد أن الإجابة هي 15 مو 13 .. أي مثل اجابة ال Release ...

لأن المعاملات ++ عند اضافتها قبل اسم المتحول فإنها من الفروض أن تحسب قبل حساب التعبير بشكل كامل ...
أي لدينا هنا ثلاث معاملا زيادة وبالتالي القيمة 2 = A تصبح A = 5 وبعدها تتم عملية الجمع ... وهذا ما أعتقد ما هو موجود في كتب السي القياسية ... وأعتقد هذا خطأ في نسخة Visual C++6 مع أنه عندي آخر Service Pack من فيجوال ستيديو وهو SP5 ..
آل عيد
شكرا لك أخي العزيز Lizardking على التوضيح ، وفي الحقيقة أعلم أن مثل هذه الشفرات لا تأدي إلا إلى الإرورات والوورينقات وتدويغ الرأس :eek: ولكنني أردت أن أتقلم أكثر مع اللغة لأني لسه جديد عليها .

ولكن بالنسبة لتغيير الإعداد قمت بذلك وفعلا أصبح الناتج 15 وليس 13 وهذا معناه أن الكومبايلر قدم عملية ++ في الأمر كله قبل عملية الجمع فأصبح 5+5+5 والناتج 15 ولكنني لا أستطيع أن أضع تفسيرا لأنني لا أعرف ما الفرق بين ضبط الإعدادات على Debug أو Release فإذا ممكن أحد يوضح لي الفرق لو سمحتوا .

الأخ الأستاذ هاني في الحقيقة قمت بتجربة نفس الكود تقريبا مع Turbo C و ++Turbo C و ++Borland C وكانت النتيجة 12 ، وبالنسبة لي مع مستواي المتواضع أعتقد أن ذلك أكثر منطقية وذلك حسب قاعدة الحساب من اليسار إلى اليمين ولكن يبدو أن مايكروسوفت غيرت أسلوب حساب مثل هذه العبارات في VC كما أوضح الأخ Lizardking .


ملاحظة: لماذا ظهر الكود الذي كتبته بشكل غير أكواد الأخوة الأعزاء ؟
هاني الأتاسي
أخي العزيز آل عيد .. بصراحة عندما وجدت الاجابة في Borland C مختلفة ، فحاولت في أن أعرف السبب .. فقمت ببنتاء class اسمه cint مشابه في عمله ل int ولكن قمت بتحميل العاملين ++ و + فيه بحيث يتم طباعة رسالة عند تنفيذ أي منهما ...

وجدت أن الاحابة 15 هي الأكثر منطقية أما الاجابة 12 فقد تكون صحيحة ولكنها خطأ وسوف تعرف لماذا أما الاجابة 13 فهي خطأ مية في المية ..

وهذا هو الكود :
[code2]
#include

class cint {
friend ostream& operator <<(ostream& os, const cint& arg) {
return os << arg.m_val;
}
public:
cint(int val = 0) : m_val(val) {}

// Prefix ++ operator
// Remove this reference and see what is the answer
cint& operator ++() {
cout << "prefix (++) : old value " << m_val;
m_val++;
cout << " new value " << m_val << endl;
return *this;
}

// Postfix ++ operator
cint operator ++(int) {
cint temp(m_val);
cout << "postfix (++) : old value " << m_val;
m_val++;
cout << " new value " << m_val << endl;
return temp;
}

cint operator +(const cint& arg) {
cout << "(+) : adding -> " << m_val << " + " << arg.m_val << endl;
return cint(m_val + arg.m_val);
}

private:
int m_val;
};


int main()
{
cint a(2), y;

y = ++a + ++a + ++a;

cout << "The result is y = " << y << " and a = " << a << endl;

return 0;
}
[/code2]

وكانت النتيجة بالصورة التالية :
[code2]
prefix (++) : old value 2 new value 3
prefix (++) : old value 3 new value 4
prefix (++) : old value 4 new value 5
(+) : adding -> 5 + 5
(+) : adding -> 10 + 5
The result is y = 15 and a = 5
Press any key to continue
[/code2]

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

لاحظ في الكود في الأعلى كيف أن العامل ++ في الكود الأول (Prefix) ترجع reference ولكن حاول أن تزبل هذا ال reference وشغل البرنامج فإنك سوف تحصل على 12 وذلك لأنك بعد الزبادة تقوم بنسخ القيمة في متحول جديد في الذاكرة أما عند استخدام ال reference فإنك تقوم بتراكم الزيادة في نفس الحجرة .. ولكن في الحالتين تم حساب ++ قبل التعبير ..

الآن أيهما الأصح استخدام reference أم من غير reference ..؟
الإجابة على هذا السؤال بسيطة جدا .. وهي طبعا الreference ضروري هنا وذلك بسبب حالة مثل هذه (cascading) :
بفرض ان x يحتوي قيمة 2 فإن هذا التعبير :
x++++ من المفروض أن ترجع 4 لأنه يمكن كتابة التعبير كالتالي : (x++)++ أولا سوف يضاف واحد إلى x ونفس هذه ال x سوف يضاف لها واحد آخر ..
لكن على فرض أن ال reference غير موجدود فالتعبير الذي بين القوسين سوف يضيف واحد إلى x بنجاح ولكنه سوف يعيد هذه القيمة في متحول جديد وهذا المتحول الجديد هو الذي يزاد بالقيمة واحد وسوف تحصل في هذه الحالة على x وتساوي 3 >>
أي ال reference ضوري في مثل هذه العوامل ومنه نستنتج أن الاجابة هي 15 ...

وشكرا ..
هذه "نسخة - خفيفة" من محتويات الرئيسية للإستعراض الكامل مع المزيد من الصور والخيارات الرجاء إضغط هنا.
Invision Power Board © 2001-2009 Invision Power Services, Inc.