گنجه
۰۱ ۲۷م, ۱۳۸۸
مبنای بحث در این مطلب، زبانهای C++ و C هستند، ولی مطالعهٔ آن برای برنامهنویسان هر زبانی که در معرض ایراد0های فساد حافظه1 است و حتی هر برنامهنویسی، احتمالاً خالی از سود نخواهد بود.
برنامهای نوشتهاید که ایراد داره. اون رو حسابی آزمودهاید و هر ایراد منطقی2 رو که فکر میکنید ممکن هست وجود داشته باشه بررسی و رفع کردهاید، ولی هنوز برنامه ایراد داره!
برنامهٔ شما رفتارهای عجیبی از خودش نشون میده. مثلاً اگر در حالت ایرادزدایی3 بسازیدش4 کار میکنه، ولی در حالت انتشار5 یا اشتباه کار میکنه، یا کار نمیکنه و دچار سانحه6 میشه. یا مثلاً با ورودیهای مختلف، در جاهای کاملاً متفاوت و نسبتاً بیربط از کد دچار سانحه میشه. یا مثلاً همیشه در پایان یک تابع7 بعد از همهٔ عملیات، ولی قبل از بازگشت (یعنی دقیقاً روی { !!!) دچار سانحه میشه.
در چنین شرایطی هست که شما حدس میزنید برنامهتون دچار ایراد فساد حافظه1 شده.
اگر برنامه همیشه در یک تابع خاص دچار سانحه میشه، حدس میزنیم که در یکی از متغیرهای محلی همون تابع خرابکاری شده و پشته8 خراب شده؛ به همین دلیل در هنگام جمع کردن قاب پشتهٔ9 تابع از روی پشته، به علت فساد نشانی بازگشت10، برنامه دچار سانحه میشه11.12 معمولاً با خوندن دقیق همون تابع، ایراد یافت و حل میشه. اگر نشد، متغیرهای محلی که روی پشته هستند رو به کومه13 منتقل میکنیم (با استفاده از new یا malloc) و میریم به سراغ روشهای مربوط به فساد کومه که الان میخواهیم بگیم.
اولین قدم برای یافتن ایرادهای فساد حافظهٔ کومه، استمداد از حضرت Valgrind است (که در دبین14 بستهاش valgrind است)):
% valgrind --leak-check=full --leak-resolution=high ./buggy
برنامه با سرعتی بسیار کمتر از حد معمول اجرا میشه و هر دسترسی به حافظهٔ کومه که خارج از حافظهٔ تخصیصیافته15 باشه، با نام تابع و (در حالت ایرادزدایی) شمارهٔ خط کد منبع16 گزارش میشه. البته Valgrind بسیار جای حرف و معرفی و ستایش و غیره داره که از موضوع این مطلب خارجه!
اگر Valgrind مشکل رو یافت و ایراد رفع شد که خدا رو شکر میکنیم! اما اگر نه، با یک مورد بدخیم (!) طرف هستیم. یعنی دسترسیهایی داریم که به رغم ایراد داشتنشون، داخل حافظهٔ تخصیصیافته قرار گرفتهاند. مثلاً دو آرایهٔ ۱۰۰تایی پشت سر هم در کومه گرفتهایم و به عنصر ۱۲۵ام از آرایهٔ اول دست زدهایم! دسترسی هنوز داخل حافظهٔ پردازهٔ17 خودمان است، ولی نادرست و ایراددار. اگر کسی تا این جای مطلب رو خونده باشه تعجب میکنم! البته کسی به جز خودم!
انشاء الله در مطلب بعدی شیوهای برای یافتن این موارد بدخیم معرفی خواهد شد که از یکی از همکاران باتجربهترم (حفظه الله) آموختهام.
- Bug [↩]
- Memory corruption [↩] [↩]
- Logical [↩]
- Debug [↩]
- Build [↩]
- Release [↩]
- Crash [↩]
- Function [↩]
- Stack [↩]
- Stack frame [↩]
- Return address [↩]
- نوعی سرریز میانگیر (Buffer overflow) [↩]
- البته این رفتار سانحهآمیز (!) بستگی به همگردان داره، ولی همگردانهای جدید مثل GCCهای ۴ به بعد (و شاید بعضی قدیمیترها)، به علت داشتن روشهایی برای مقابله با خرابی پشته، عمداً برنامهای تولید میکنند که در این شرایط دچار سانحه بشه [↩]
- Heap [↩]
- Debian [↩]
- Allocated [↩]
- Source code [↩]
- Process [↩]
۱۲ ۱۳م, ۱۳۸۷
یکی از روشهای نسبتاً متفاوت همگردان0ها برای بهینهسازی1، روش «بهینهسازی هدایتشده با سابقه»2 است که گاهی اون رو کوتاهانه3 PGO هم میگویند.
مؤثر بودن این روش رو میشه به چشم دید. مثلاً اگر از مرورگر Firefox هم در لینوکس4 و هم در ویندوز5 استفاده کرده باشید، حتماً تفاوت فاحش کارآیی6 رو حس کردهاید. دلیل این تفاوت فاحش، استفاده از PGO در ساخت7 پروندهٔ8 اجرایی9 ویندوز و عدم استفاده ازش در ساخت اجرایی لینوکس بیان شده.
خوب، حالا که به اندازهٔ کافی انگیزوندمتون (!)، ببینیم این روش اصلاً چی هست و چهجوری میتونیم ازش استفاده کنیم.
اساس این روش، استفاده از اطلاعات کسبشده در زمان اجرا10ی برنامه، برای بهینهسازی در زمان همگردانی11 هست. پس همگردانی با این روش، سه مرحله خواهد داشت:
۱. همگردانی برای تولید اجرایی مخصوص جمعآوری اطلاعات مورد نیاز برای بهینهسازی
۲. اجرای برنامهٔ تولیدشده در مرحلهٔ ۱ و استفاده از موارد کاربرد12 مورد نظر برای بهینهسازی
۳. همگردانی با بهینهسازی به کمک اطلاعات جمعآوریشده در مرحلهٔ ۲
همگردانها برای مراحل ۱ و ۳ گزینههای مخصوصی دارند که اینجا فقط GCC رو میبینیم.
درGCC برای مرحلهٔ ۱ از گزینهٔ -fprofile-generate و برای مرحلهٔ ۳ از گزینهٔ -fprofile-use استفاده میکنیم. دقت کنید که گزینهٔ -fprofile-generate رو هم باید برای همگردانی13 بدید، و هم برای پیوند14.
من که معمولاً از CMake به عنوان سامانهٔ ساخت15 استفاده میکنم، برای به کارگیری این روش، این چند خط رو به CMakeLists.txt اضافه میکنم:
# profile::generate
#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --profile-generate")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --profile-generate")
#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --profile-generate")
# profile::use
#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --profile-use")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --profile-use")
#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --profile-use")برای مرحلهٔ ۱، سه تا set اول و برای مرحلهٔ ۳، سه تا set دوم رو فعال میکنم. راستی دقت کردید که به جای -fprofile-folan میشه از -profile-folan- استفاده کرد؟!
۱۲ ۱۲م, ۱۳۸۷
با پرهیز از Java و .NET (در مقابل C++ و C) در روند گرمایش زمین سهم کمتری داشته باشیم.
۰۹ ۳م, ۱۳۸۷
اگر برنامهنویس C++ باشی، حتماً تا حالا مورد آزار و اذیت پیغامهای خطای سرسامآور همگردان0 در مورد STL قرار گرفتهای. مثلاً این:
testmm2.cpp:31: error: no matching function for call to ‘std::multimap<int*, int, std::less<int*>, std::allocator<std::pair<int* const, int> > >::insert(int)’ /usr/include/c++/4.3/bits/stl_multimap.h:407: note: candidates are: typename std::_Rb_tree<_Key, std::pair<const _Key, _Tp>, std::_Select1st<std::pair<const_Key, _Tp> >, _Compare, typename _Alloc::rebind<std::pair<const _Key, _Tp> >::other>::iterator std::multimap<_Key, _Tp, _Compare, _Alloc>::insert(const std::pair<const _Key, _Tp>&) [with _Key = int*, _Tp = int, _Compare = std::less<int*>, _Alloc = std::allocator<std::pair<int* const, int> >] /usr/include/c++/4.3/bits/stl_multimap.h:431: note: typename std::_Rb_tree<_Key, std::pair<const _Key, _Tp>, std::_Select1st<std::pair<const _Key, _Tp> >, _Compare, typename _Alloc::rebind<std::pair<const _Key, _Tp> >::other>::iterator std::multimap<_Key, _Tp, _Compare, _Alloc>::insert(typename std::_Rb_tree<_Key, std::pair<const _Key, _Tp>, std::_Select1st<std::pair<const _Key, _Tp> >, _Compare, typename _Alloc::rebind<std::pair<const _Key, _Tp> >::other>::iterator, const std::pair<const _Key, _Tp>&) [with _Key = int*, _Tp = int, _Compare = std::less<int*>, _Alloc = std::allocator<std::pair<int* const, int> >]
حالا این یکی بود. وقتی ده-پونزده تا از اینها یکجا رخ میده، خیلی افتضاح میشه.
مدتها با این مشکل کنار اومده بودم و دردش رو تحمل میکردم. اما اخیراً یک راه نجات یافتم: ابزاری به نام STLFilt. یک نرمافزار آزاد هست که خروجی همگردان C++ رو میگیره و اون پیغامهای وحشتناک رو مهربون میکنه. مثلاً پیغام بالا رو تبدیل میکنه به این:
testmm2.cpp:31: error: No match for ‘multimap<int *, int>::insert(int)’
stl_multimap.h:407: candidates are: map<int *, int>::iter multimap<int *, int>
::insert(const pair<const int *, int> &)
stl_multimap.h:431: map<
int *, int
>::iter multimap<
int *, int
>::insert(map<int *, int>::iter, const pair<const int *, int> &)آخش! حالا میشه خوندش!
برای استفاده از STLFilt اول باید بستهٔ مربوط به همگردانت رو بگیری. مثلاً gstlfilt.zip برای G++. توش کلی پرونده هست که همه رو بیخیال میشی به جز gSTLFilt.pl که اصل کار رو انجام میده. از stdin میخونه و توی stdout مینویسه. کاری که من کردم این بود1:
sudo su F=/usr/local/bin/stlfilt.pl echo '#!/usr/bin/perl' >$F cat gSTLFilt.pl >>$F chmod +x $F
و برای استفاده:
make 2>&1 | stlfilt.pl
برای این که هر بار نخوام stderr دستور ساخت2 (مثلاً make) رو به stdoutش redirect3 کنم، دستنوشتهٔ4 /usr/local/bin/stlfilt رو با این محتوا ساختم:
#!/bin/sh "$@" 2>&1 | stlfilt.pl
و chmod +x هم یادم نره. ![]()
حالا اینجوری استفاده میشه:
stlfilt makeبا دستنوشتهها زندگی زیباتره!
ویرایش: امید آن میرود که با معرفی «مفهوم»5ها در C++0x (به زودی!) کل مشکل از بیخ حل بشه و دیگه نیازی به ابزار اضافی نباشه.
۰۸ ۱۷م, ۱۳۸۷
سلام!
1 2 3 4 5 6 7 8 9 10 | // In the name of God // بسم الله الرحمن الرحیم #include <iostream> int main() { std::cout << "Salaam World!" << std::endl; return 0; } |
ویرایش: اول قصد داشتم توی این مطلب آزمایشهام را انجام بدم و بعد هم حذفش کنم. اما کار از کار گذشت و دوستان اومدند نظر دادند! این شد که همین مطلب را کمی ویرایش کردم و گذاشتم تا سرآغاز این تارنوشت بمونه.
برای بیشتر دونستن در مورد این تارنوشت، صفحهٔ دربارهٔ گنجه را بخونید.