سلام به دوستان عزیز.
نیت کرده بودم پست ی در مورد Memory Leak بنویسم و در این پست بیشتر باابن باگ آشنا میشیم و حرف میزنیم و کدها بصورت Native code و دلفی هستش (بخش اول) که بعدا سراغ managed code ها هم میریم (بخش دوم).
وقتی که یک برنامه فضایی از حافظه رو در اختیار میگیره و به خودش اختصاص میده، مثلا ساخت یک نمونه از کلاس ها و ... اگر نتونه اون فضای اختصاص یافته رو آزاد کنه این باگ اتفاق میافته که ممکن است با اجرای زیاد برنامه فضای زیادی از حافظه اشغال شده و ازاد نشه و با کمبود حافظه مواجه بشیم. این باگ توسط برنامه نویس قابل رفع هست. نمونه خیلی ساده که بگم نوشتن برنامه multi-thread اگر به درستی نتونید ترید های ساخته شده رو free کنید حافظه بیخود اشغال میشه.
این باگ رو برای برای موارد خاصی در نظر میگیریم ، مثلا برنامه هایی که در سرور ها اجرا میشن و ممکن است چندسال در حال جرا باشند که اگر همچین باگی در برنامه باشد بعد ازمدتی کل کارها میخابه بعلت اشغال حافظه بیخود و شاید به حد ریستارت نمودن کل سیستم بکشه.
بزارین با یک سورس کد بهتر متوجه بشیم که چگونه این باگ اتفاق میافته، در زیر یک سورس ساده از دلفی هست
procedure PrintFile(const fileName : string); var myFile : TextFile; fileData : TStringList; i : Integer; begin // Create the TSTringList object fileData := TStringList.Create; // This allocates object memory // Load the file contents into the string list fileData.LoadFromFile(fileName); // Expands the object memory size // Open a printer file AssignPrn(myFile); // Now prepare to write to the printer ReWrite(myFile); // Write the lines of the file to the printer for i := 0 to fileData.Count-1 do WriteLn(myFile, fileData[i]); // Close the print file CloseFile(myFile); end;
به نظرتون باگ memory leak کجا داره اتفاق میافته؟
قبلا گفتیم فضایی از حافظه گرفته بشه و برنامه نویس اون فضا رو آزاد نکنه این باگ رخ میده، پس در سورس بالا میشه متغیر fileData که نوعی از TStringList هست، هربار که procedure فراخوانی میشه یک فضایی مجدد به این متغیر اختصاص پیدا میکنه در انتها آزاد نمیشه، بنابراین برای جلوگیری از این اتفاق بایستی در انتهای پروسیجر خط زیر رو بنویسیم تا فضای اختصاص پیدا شده رو آزد کنیم:
// Free up the string list memory FreeAndNil(fileData);
این یک مشکل عمده هستش که برنامه نویس های مبتدی دارن، مخصوصا کسانیکه اصلابرنامه نویسی براشون مد نظر نیست فقط خروجی داشته باشن و بس، که واقعا برنامه نویس توی مملکت زیاده ولی کسی که برنامه نویس اصولی یا همون حرفه ای باشه کمه.این مشکل بیشتر در زبان هایی اتفاق میافته که Garbage collector ویا GC نداشته باشند.بهمین دلیل ابزارها و کامپوننت هایی نوشته شده که دوستان برنامه نویس خاص، برای راحتی کارشون از اونا استفاده میکنند مثل : IBM Rational Purify, BoundsChecker, Valgrind, Insure++, Dr. Memory, memwatch,madExcept و ..
و همچنین برنامه های بعنوان Memory Optimizer هم نوشته شده اند و در دسترس هستند که تا فضای از حافظه که بیخود اشغال شده اند رو آزاد کنند. یادمه یک تولز نوشته بودم و برای تسریع کار از یک یونیت استفاده کردم، در تست برنامه اینقدر داغون میشد که دیده شده بود سیستم کارش به restart کشیده میشد! پس حواستون هم به یونیت ها که استفاده میکنید و خودتون ننوشتین باشه تا به مشکل نخورین.
در بحث OOP کلاس دارای یک سازنده constructor هستند و همچنین یک تابع مخرب Destructor . خوب وظیفه شما اینه که در Destructor کلاستون تمامی فضایی که اشغال شده رو آزاد کنید.
به مثال زیر توجه کنین:
unit MyClass; interface uses Classes; type TMyClass = class private fileData : TStringList; published Constructor Create(const fileName : string); Destructor Destroy; override; procedure PrintFile; end; implementation uses Printers, Dialogs, SysUtils; // Constructor - builds the class object constructor TMyClass.Create(const fileName: string); begin // Create the string list object used to hold the file contents fileData := TStringList.Create; // And load the file data into it try fileData.LoadFromFile(fileName); except ShowMessage('Error : '+fileName+' file not found.'); end; end; // Destructor - frees up memory used by the class object destructor TMyClass.Destroy; begin // Free up the memory used by the string list object FreeAndNil(fileData); // Call the parent class destructor inherited; end; // Print the file passed to the constructor procedure TMyClass.PrintFile; var myFile : TextFile; i : Integer; begin // Open a printer file AssignPrn(myFile); // Now prepare to write to the printer ReWrite(myFile); // Write the lines of the file to the printer for i := 0 to fileData.Count-1 do WriteLn(myFile, fileData[i]); // Close the print file CloseFile(myFile); end; end.
توجه کنید که در این مثال object ی که محتویات فایل را در خود نگه میدارد در تابع PrintFile هم استفاده می شود. و هر تعداد بار تابع فراخوانی شود مقدار تخصیص حافظه پایدار است و تغییر آنچنان نمیکند زیرا در سازنده کلاس عملیات تخصیص حافظه انجام شده است. اگر instance ساخته شده از کلاس از بین برود string list که محتویات فایل را نگه داشته است نیز ازبین خواهد رفت و حافظه آزاد میگردد.
خوب در تابع PrintFile یک متغیر لوکال به اسم myFile داریم و در همون تابع عمل آزاد سازی حافظه رو انجام میدهیم ولی متغیر fileData بصورت عمومی در کلاس وجود دارد، پس بایستی در مخرب کلاس فضای حافظه رو آزاد کنیم که مطمئن شویم هیچ دسترسی به آن نیست و بعدا نیز نیازی به آن نخواهد بود.
راه دیگ در توابع که خیلی استفاده میشه و پرکاربرد هست try / finally است. با ااستفاده از این بلاک مدیریت exception ها در صورت بروز خطا مطمئن شوید که حافظه موردنظرتان آزاد شده است. مثلا یک نمونه سورس در زیر میبینید
var F: TextFile; S: string; begin AssignFile(F, 'c:\RinzER0.txt') ; try Readln(F, S) ; finally CloseFile(F) ; end; end;
که در صورت بروز هرگونه خطا در بلاک try میتونین مطمئن باشین بلاک finally اجرا میشه.
تشخیص MemoryLeak و گزارش آن بصورت پیش فرض در دلفی غیرفعال هست. برای فعال سازی این قابلیت بایستی مقدار ReportMemoryLeaksOnShutdown را True نماییم. با فعال کردن این قابلیت وقتی برنامه بسته شود پنجره "Unexpected Memory Leak" نمایش داده می شود که گزارشی از MemoryLeak در مدت اجرای برنامه می باشد.برای فعال نمودن این قابلیت بایستی کد زیر را در فایل *.dpr پروژه اضافه کنید :
ReportMemoryLeaksOnShutdown := True;
یعنی چیزی شبیه به حالت زیر:
begin
ReportMemoryLeaksOnShutdown := True;
Application.Initialize;
Application.Title := 'www.Ring0.blog.ir';
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
در یکی از برنامه هایی که نوشته بودم کاملا بخش های Memory Managment رو حذف کردم و در عرض چندثانیه این مقدارها نشان داده شد. البته عکس رو از وسط cut زدم که کوچکتر بشه ...
حالا فکرکنید این برنامه برای مدتی طولانی در حال اجراباشه چه اتفاقی میافته؟!؟ پس اصولی برنامه نویسی کنیم.
- ۹۲/۰۱/۰۵