jmp Ring_ZerO

; go to ring zero
نوشته شخصی و فنی

jmp Ring_ZerO

نوشته شخصی و فنی

  • ۱
  • ۰

MemoryLeak چیست؟

 سلام به دوستان عزیز.

نیت کرده بودم پست ی در مورد 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 زدم که کوچکتر بشه ...

حالا فکرکنید این برنامه برای مدتی طولانی در حال اجراباشه چه اتفاقی میافته؟!؟ پس اصولی برنامه نویسی کنیم.

  • ۹۲/۰۱/۰۵

نظرات (۲)

ممنون بابت زحمتی که واسه آموختن اصولی برنامه نویسی می کشید.
برای آزاد کردن حافظه کلاس stringlist شما از freeandnil  استفاده کردین.آیا این تابع تفاوتی با تابع free  مرسوم در کلاس stringslist هم داره؟
یا دلیل خاصی در استفاده از این تابع دارین؟
پاسخ:
سلام دوست عزیز.

خیلی ها فکر میکننند این قاعده freeandnil=free+nil درست هست در اصل اگر تابع freeandnil  رو نگاه کنید اینطور نیست.
procedure FreeAndNil(var Obj);
{$IF not Defined(AUTOREFCOUNT)}
var
  Temp: TObject;
begin
  Temp := TObject(Obj);
  Pointer(Obj) := nil;
  Temp.Free;
end;
{$ELSE}
begin
  TObject(Obj) := nil;
end;
{$ENDIF}


ابتدا object مان nil شده و سپس توسط temp آبجکت مان free می شود. دلیل اصلی استفاده من از freeandnil؛ اعتماد حاصل کردن از اینکه variable مان بعد از free شدن nil شده است، طبق یه عادت قدیمی.  سورس زیر رو ببینید:
var
  fileData : TStringList;
begin
  fileData := TStringList.Create;
  fileData .Free;

  if Assigned(fileData ) then
    FreeAndNil(fileData);

بعد از اجرا شدن و همچنین Free شدن stringlist مون بازهم مقدار Assigned هم True خواهد بود، و اگه سورس بالا رو اجرا کنید exception رخ میده (EInvalidPointer). خوبه که اشاره گره های سرگردان (Dangling Pointers) توی برنامه هامون ایجاد نکنیم. خوب وقتی که nil بشه سریعتر از عواقب بعدی میشه جلوگیری کرد. بزرگان MVP هم بعضی ها میگن freeandnil رو اصلا استفاده نکنید،بعضی هام میگن تا میتونید استفاده کنید، بعضی ها هم میگن معتدل و هرچیزی درجای خود استفاده بشه.

در استفاده از این دستور باید دقت داشته باشیم که object به این تابع پاس میدهیم از Tobject ارث بری شده باشه. توی عکس گزارش میبینیم که رشته هایی رو بعنوان memory leak ذکر کرده، این بدین معنی نیستش که فراموش کردیم جایی String مون رو free  کنیم، این به این معنی هستش که ما فراموش کردیم owner رشته مون که به احتمال یک object هست رو free کنیم.

ممنون.
پاسخ کاملی دادین و اشاره گرهای سرکردان واسم تازه بود.
 FreeAndNil منطقی تر به نظر میاد و دلیلی برای استفاده نکردن !!؟؟
پاسخ:
خواهش.

Nick Hodges همچنین نظری داره :

All code should be written with “considered intent,” and the indiscriminate use of FreeAndNil shows a lack of consideration and intent.

میتونید در این آدرس مقاله ای که ایشون در این مورد نوشتن و دلایلشون رو ذکر کردن بخونین [+]


ارسال نظر

ارسال نظر آزاد است، اما اگر قبلا در بیان ثبت نام کرده اید می توانید ابتدا وارد شوید.
شما میتوانید از این تگهای html استفاده کنید:
<b> یا <strong>، <em> یا <i>، <u>، <strike> یا <s>، <sup>، <sub>، <blockquote>، <code>، <pre>، <hr>، <br>، <p>، <a href="" title="">، <span style="">، <div align="">
تجدید کد امنیتی