PDA

View Full Version : گفتگو: بحث در مورد نحوه محاسبه int y = x + 4 * ++x توسط کامپایلر #C



Amir Oveisi
یک شنبه 01 آبان 1390, 14:24 عصر
دیشب یکی از دوستان سوالی مطرح کرد در یک محفلی مبنی بر اینکه اگر مقدار x مساوی دو باشه مقدار عبارت int y = x + 4 * ++x چه چیزی خواهد بود؟ نظر ها داده شد و بحث ها ادامه پیدا کرد تا اینکه متوجه شدیم کامپایلر #C مقدار ۱۴ رو بدست میاره در حالی که کامپایلر های دیگه مثل C و CPP و Java هم روی لینوکس و هم روی ویندوز مقدار ۱۵ بدست میارن و اصولا هم باید مقدارش ۱۵ باشه. در تلاش برای فهمیدن اینکه چرا این اتفاق میفته کد های IL اش رو بررسی کردیم:
عبارت مورد نظر:
int x = 2;
int y = x + 4 * ++x;

کدهای IL بدست اومده:
locals init ([0] int32 x,
[1] int32 y)
IL_0000: nop
IL_0001: ldc.i4.2
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: ldc.i4.4
IL_0005: ldloc.0
IL_0006: ldc.i4.1
IL_0007: add
IL_0008: dup
IL_0009: stloc.0
IL_000a: mul
IL_000b: add
IL_000c: stloc.1


خط های ۳ تا ۶ به ترتیب مقدارهای ۲ و ۴ و ۲ و ۱ رو میریزه تو استک.
خط ۷ دو تا از بالای استک بر میداره با هم جمع می کنه و دوباره میزاره تو استک. در وافع همون x++.
الان تو استک داریم: (از پایین به بالا) ۲ و ۴ و ۳.
خط ۸ یه کپی از ۳ میریزه تو استک و میشه ۲ و ۴ و ۳ و۳.
خط ۹ : x = 3 و استک میشه ۲ و ۴و ۳.
خط a: سه ضربدر چهار میکنه و استک میشه 2 , 12.
در وافع ۴ *‌ x++.
مشکل سر خط b‌هست که میاد ۲ رو با ۱۲ جمع میکنه در حالی که الان x سه هست اما چون تو خط ۳ مقدار قبلی xرو ریخته تو استک و بعدا مقدارش رو تو استک update نکرده پس ناچارا ۱۲ با ۲ جمع میشه به جای ۳ و حاصل میشه ۱۴.

دوستان نظری در مورد اینکه چرا به این شکل عمل میشه دارید؟

F.zeinali
یک شنبه 01 آبان 1390, 19:40 عصر
سلام
من فکر می کنم C#‎به روش زیر محاسبه می کنه(با کدهای IL کاری ندارم)
طبق همون چیزی که در تقدم عملگرها داریم
اول x++ رو حساب می کنه و میشه 3
بعد 3 رو با 4 ضرب میکنه حاصل میشه 12
بعد 12 رو با 3جمع میکنه حاصل میشه 15

Amir Oveisi
یک شنبه 01 آبان 1390, 19:49 عصر
مساله اینجاست که اگر این رو با #C تست کنید جواب ۱۴ میشه :)

یعنی اول x++ رو حساب میکنه بعد ضرب در ۴ میکنه. بعد با x جمع میکنه. اما وقتی x++ رو انجام داده مقدار x اول رو در هنگام جمع کردن کماکان ۲ حساب میکنه در حالی که باید ۳ باشه.

F.zeinali
یک شنبه 01 آبان 1390, 19:55 عصر
من الان BreakPoint که گذاشتم x اول در خط دوم رو 3 گذاشته اما چطور 14 در میاد!!!!!!!

Amir Oveisi
یک شنبه 01 آبان 1390, 20:03 عصر
با breakpoint‌به نتیجه نمیرسید چون نمیتونید بفهمید اول کدومش رو داره حساب می کنه. اگر هم بخواید از Quick Watch‌ استفاده کنید که دیگه بدتر میشه و همه محاسبات به هم میریزه چون استفاده از Quick Watch ترتیب اجرا رو به هم میریزه و اول اون تیکه رو که شما watch میکنید رو حساب می کنه بعد که میزنید بره خط بعدی دوباره نتایج به جا مونده از watch‌رو تو محاسبه استفاده می کنه نه مقادیر اصلی.

Amir Oveisi
یک شنبه 01 آبان 1390, 20:06 عصر
ببینید بحث هم سر همینه. تو کدهای IL‌ اگر دقت کنید کاملا مشخصه. اونجا هم مقدار x رو به ۳ تغییر میده اما آخر سر که میخواد جمع کنه مقدار x‌ رو از x‌نمیخونه بلکه مقدار قبلی x (اون موقع که ۲ بود) رو که قبلا گذاشته بود تو استک میخونه و جمع میکنه.

S.Reza
یک شنبه 01 آبان 1390, 20:26 عصر
این مشکل رو من قبلا داشتم

توی یکی از برنامه ها مقداری که توسط کامپایلر سی شارپ بدست می امد با اون مقداری که خودم بدست می اوردم یکی نبود بعد بر حسب اتفاق اون برنامه رو با زبان سی نوشتم دقیقا همون کدی که در سی شارپ نوشتم در سی هم نوشتم اما در کمال تعجب جواب یکی نبودند :متعجب:

پی گیر شدم اما در منابع فارسی چیزی پیدا نکردم تا اینکه با کمی تغییر در فرمول توانستم جواب ها رو یکی کنم

نکته ای که یاد گرفتم این بود که اگر قراره مقداری رو ++ یا -- کنم باید اول فرمول از اون استفاده کنم نه اخر فرمول مثلا الان اگر شما فرمول بالا رو به شکل زیر بنویسید باید درست جواب بده ( الان کامپایلر سی شارپ ندارم که امتحان کنم )



int y= ++x * 4 + x


اما هیچ وقت نفهمیدم مشکل اصلی کجاست :متفکر:

Amir Oveisi
یک شنبه 01 آبان 1390, 22:52 عصر
بله در این حالت درست جواب میده. چون وقتی این شکلی نوشته میشه مقدار تغییر یافته x رو استفاده می کنه.
کد IL به این شکل میشه:
.locals init ([0] int32 x,
[1] int32 y)
IL_0000: nop
IL_0001: ldc.i4.2
IL_0002: stloc.0
IL_0003: ldc.i4.4
IL_0004: ldloc.0
IL_0005: ldc.i4.1
IL_0006: add
IL_0007: dup
IL_0008: stloc.0
IL_0009: mul
IL_000a: ldloc.0
IL_000b: add
IL_000c: stloc.1

دستور خط a مقدار x = 3 رو میاره رو استک و با ۱۲ جمع میشه. اما تو حالت قبلی مقدار تغییر یافته X به حال خودش رها میشه چون قبلش تو استک مقدار قدیمیش بوده با همون جمع میکنه ۱۲ رو. دلیل این اتفاق برام روشن نیست هنوز. آیا منطقی پشتشه؟

mehdi.mousavi
دوشنبه 02 آبان 1390, 11:43 صبح
سلام.
در عبارات C#‎‎، هر sub-expression ای ابتدا بصورت منطقی و بر اساس اولویت operator ها (http://msdn.microsoft.com/en-us/library/aa691323%28v=vs.71%29.aspx)، دسته بندی میشه. به بیان دیگه:


int x = 2;
int y = x + 4 * ++x;

int level1 = x + 4 * (++x);
int level2 = x + (4 * (++x));
int level3 = (x) + (4 * (++x));


پس اینجا 3 تا sub-expression داریم، که مقادیر هر یک جداگانه محاسبه میشه. سپس، کلیه این sub-expression ها (همواره) از سمت چپ به راست، پردازش میشن. از سمت چپ که شروع کنیم، حاصل عبارت میشه 14. اما در C++‎‎ اینگونه نیست. اما Sub-Expression ها در C++‎‎ ممکنه گاهی از راست به چپ و گاهی چپ به راست Evaluate بشن. به این مثال (http://stackoverflow.com/questions/3457967/what-belongs-in-an-educational-tool-to-demonstrate-the-unwarranted-assumptions-p/3458842#3458842) دقت کنید. این برنامه ممکنه Hello World یا World Hello رو در خروجی چاپ کنه، چون در استاندارد تعیین نشده که یک SubExpression بر چه اساسی محاسبه میشه. چون همون عبارت، در C++‎‎، از راست به چپ داره Evaluate میشه، به مقدار 15 میرسیم. یعنی مقدار جدید x (یا همون 3) هنگام عمل جمع دو Operand اول مورد استفاده قرار میگیره.

موفق باشید.


پاورقی: برای اطلاعات و لینک های بیشتر، لطفا به این آدرس (http://stackoverflow.com/questions/6457130/pre-post-increment-operator-behavior-in-c-c-java-c-sharp) رجوع کنید.

Amir Oveisi
دوشنبه 02 آبان 1390, 12:39 عصر
ممنون از توضیحتون. من اینارو تو سایت های خارجی خوندم اما برام قانع کننده نبود. یعنی به نظرم ایراد داره این قضیه. اگر اول x++‌محاسبه میشه در واقع x = x + 1‌ مگه نمیشه؟‌ و دو تا x‌ هم یک متغیر مگه نیستن؟ پس چرا بعد از اینکه X مقدارش زیاد میشه اون x اول عبارت با مقدار قبلیش تو محاسبه استفاده میشه؟ (مگر اینکه مقدار هر sub-expression‌ مستقل بوده باشه که باز اینم خوب به نظر نمیاد)

mehdi.mousavi
دوشنبه 02 آبان 1390, 13:58 عصر
سلام.
به گمانم نتونستم خوب توضیح بدم... لطفا کد زیر رو در C++‎‎ در نظر بگیرید:


int x = 2;
int y = x + 4 * ++x;

++، ضرب و جمع سه Operator ای هستن که در عبارت فوق وجود داره (البته در اصل چهار تا، چون = هم هست). بین ++، * و +، در حالتیکه Precedence اپراتورها یکسانه، Compiler با استفاده از Associativity تصمیم میگیره که کدوم Sub-Expression رو ابتدا انجام بده. Associativity هم تعیین میکنه که یک عبارت باید از سمت راست به چپ محاسبه بشه، یا از سمت چپ به راست. بین سه Operator فوق الذکر، چون هر سه Operator اولویت 3 دارن (http://en.cppreference.com/w/cpp/language/operator_precedence)، پس C++‎‎ مجبوره بر اساس Associativity مقدار مزبور رو بدست بیاره. در اولویت سه، Associativity در C++‎‎ از راست به چپ هستش. بنابراین، ابتدا x یکی اضافه میشه (میشه 3)، سپس در 4 ضرب میشه (میشه 12) و در نهایت مجددا با 3 جمع میشه و مقدار میشه 15.

اما در C#‎‎، (مانند C++‎‎) اون per-increment باعث ایجاد یک side-effect میشه. یعنی چی؟ یعنی x++ باعث میشه تا State محیط اجرایی تغییر کنه و x مقدار جدیدی به خودش بگیره. حالا اجازه بدید یک بار دیگه اون عبارت رو بنویسیم، االبته اینبار با توجه به Operator Precedence...

x + (4 * (++x))

حالا parse tree ی این عبارت رو در نظر بگیرید:


x
+
4
*
++x



x
+
4
*
3

اینجا، اون side effect، درست قبل از اینکه result نهایی تولید بشه رخ میده، یعنی x درسته شده سه، اما چون هنوز result نهایی تولید نشده، x در عبارت فوق هنوز مقدارش دو خواهد بود. به جمله Eric Lippert دقت کنید:

If the evaluation of a subexpression causes a side effect because of a pre or post increment subexpression then the side effect happens immediately before the result is produced.



پس وقتی y محاسبه شد، مقدار x نیز تغییر میکنه و در خط بعد، x سه خواهد بود.


موفق باشید.

پاورقی: مقدار عبارت

int y = x + (++x) + (4) * (++x);

رو بررسی کنید، کاملا متوجه موضوع میشید.

Amir Oveisi
دوشنبه 02 آبان 1390, 17:19 عصر
به گمانم نتونستم خوب توضیح بدم...
نه اتفاقا خوب گفتید ولی من از نظر فنی با قضیه مشکل ندارم و میدونم که چه اتفاقاتی مبوفته که اینجوری میشه بلکه دنبال اون منطقی هستم که پشت قضیه است. چرا به این شکل عمل شده؟ در واقع دلیل جمله زیر:

If the evaluation of a subexpression causes a side effect because of a pre or post increment subexpression then the side effect happens immediately before the result is produced.
به نظرتون این روش محاسبه به نظر غیر منطقی نمیاد؟ مشکلم با این منطقشه که برام هضم نشده هنوز. به نظرتون آیا اگر side-effect قبل از محاسبه نتیجه نهایی اعمال بشه درست تر نیست از نظر منطقی؟ یا ممکنه اینکار مشکلات دیگه ای به وجود بیاره؟

mehdi.mousavi
دوشنبه 02 آبان 1390, 17:48 عصر
به نظرتون این روش محاسبه به نظر غیر منطقی نمیاد؟ مشکلم با این منطقشه که برام هضم نشده هنوز. به نظرتون آیا اگر side-effect قبل از محاسبه نتیجه نهایی اعمال بشه درست تر نیست از نظر منطقی؟ یا ممکنه اینکار مشکلات دیگه ای به وجود بیاره؟

به گمانم انتخاب این روش، کار رو هم برای طراحان زبان و هم استفاده کنندگان از اون، "قابل پیش بینی" کرده. دلیل دیگه ای به ذهنم نمیرسه!
اجازه بدید قدری تحقیق کنم، اگر به مطلب جدیدی رسیدم همینجا باهاتون در میون میذارم.

Javad_Darvish_Amiry
پنج شنبه 05 آبان 1390, 16:52 عصر
به نظرتون این روش محاسبه به نظر غیر منطقی نمیاد؟ مشکلم با این منطقشه که برام هضم نشده هنوز. به نظرتون آیا اگر side-effect قبل از محاسبه نتیجه نهایی اعمال بشه درست تر نیست از نظر منطقی؟ یا ممکنه اینکار مشکلات دیگه ای به وجود بیاره؟
اتفاقا من هم دقیقا با همین مشکل دارم؛ تو کتم نمیره! البته هنوز بحث ادامه داره ولی چون بحث ها همه فنی هست، منطقا نمیتونه مجاب کننده باشه؛ شاید مهمترین مسئله ای که باعث این «نپذیرفتن» میشه، انتظار همگنی زبان های همخانواده باشه؛
یه نکته اضافه: در مورد جاوا که تو صورت مسئله گفتی، اینطور نیست؛ البته من یادمه که قبلا تو تستی که گرفته بودم (شاید سه یا چهار سال پیش) تو نت-بینز و لینوکس، جواب با C و ++C یکی بود؛ اما بعد از طرح مسئله دوباره حساس شدم و تست کردم، هم نت-بینز و هم جی-دولوپر، هم ویندوز و هم لینوکس، جواب با سی شارپ برابر بود. بنظر میاد تغییراتی صورت گرفته باشه. وقت نکردم برم دنبالش (شاید هم حافظه م اشتباه میکنه).

vof.ir
دوشنبه 09 آبان 1390, 14:20 عصر
جناب موسوی من منطقی را که گفتید متوجه شدم
منتهی اینکه ساید افکت بعد از عبور از خط جاری اتفاق میفتند


در این مثال صادق نیست
int a = 2;
int b = a++ + a++;
int c = ++a + a++ + a++;

+-----+------+------+----+
| C | C++‎ | Java | C#‎ |
+-----+-----+------+------+----+
| a | 7 | 7 | 7 | 7 |
+-----+-----+------+------+----+
| b | 4 | 4 | 5 | 5 |
+-----+-----+------+------+----+
| c | 15 | 15 | 16 | 16 |
+-----+-----+------+------+----+

بررسی بفرمایید.

vof.ir
یک شنبه 20 فروردین 1391, 00:08 صبح
اساتید ممکن هست در مورد این پست بالا توضیح دهید؟
ممنونم