سلام به دوستان عزیز.
نیت کرده بودم پست ی در مورد 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 زدم که کوچکتر بشه ...
حالا فکرکنید این برنامه برای مدتی طولانی در حال اجراباشه چه اتفاقی میافته؟!؟ پس اصولی برنامه نویسی کنیم.
- ۹۲/۰۱/۰۵
