PDA

View Full Version : مبتدی: جلوگیری از نشت حافظه



ehsan_faal
شنبه 30 خرداد 1394, 18:31 عصر
سلام.
آیا برای برگردوندن حافظه ای که در یک تابع اونو از هیپ گرفتیم و آدرسش رو به عنوان خروجی تابع فرستادیم به main نیازی به استفاده از delete نیست؟
در کل واسه اینکه بفهمیم کجاها حافظه از دست میره راهی هست؟یه چیزی مثل پروفایل که به جای این که بگه این خط کد رو کند میکنه بگه توی این خط حافظه از دست میدیم؟

#include <iostream>
#include <string>
#include <assert.h>
using namespace std;
char *ReverseCat(const char*, const char*);
void main(void)
{
char Str1[] = " C++‎‎‎ Language is the most powerful ";
char Str2[] = ".egaugnaL";
cout << "String1:" << Str1 << endl;
cout << "String2:" << Str2 << endl;
char *tmp = ReverseCat(Str1, Str2);
cout << "String1 + Reverse(String 2):" << tmp << endl;
cout << Str2 << endl << Str1 << endl;
delete[] tmp;
tmp = 0;
}
char *ReverseCat(const char*Dest, const char*Source)
{
//Reverse Part:
int SourceSize = strlen(Source);
char *SourceHolder = new char[SourceSize + 1];
assert(SourceHolder != 0);
memset(SourceHolder, '@', SourceSize - 1);
SourceHolder[SourceSize] = 0;
for (int index = 0, reverseIndex = (SourceSize - 1); index < SourceSize; index++, reverseIndex--)
{
SourceHolder[index] = Source[reverseIndex];
}
//Concatenate Part:
int DestSize = strlen(Dest);
int NewSize = DestSize + SourceSize + 1;
/*char *DestAddr = (char *)Dest;*/
char *DestAddr = new char[NewSize];
assert(DestAddr != 0);
memset(DestAddr, '@', NewSize - 1);
DestAddr[NewSize] = 0;
strncpy_s(DestAddr, NewSize, Dest, _TRUNCATE);
strcat_s(DestAddr, NewSize, SourceHolder);
delete[] SourceHolder;
return DestAddr;
}
من توی main که میخوام حافظه رو برگردونم این اررور میاد؟
132446

negative60
شنبه 30 خرداد 1394, 21:19 عصر
char *DestAddr = new char[NewSize+1];
هميشه يک بايت بيشتر از حجم مورد نظر حافظه اختصاص بديد

پيشنهاد:
همچنين تا جايی که امکان داره از new در توابع پر کاربرد استفاده نکنيد (مثلاً در حلقه ها) مگر اينکه واقعاً لازم باشه و سعی کنيد مثل توابع API و توابع خود ++c خروجی رو از پرامتر بگيريد اينطور نياز به new کردن هم نيست


void ReverseCat(const char *Dest, const char *Source, char *Out)

ehsan_faal
یک شنبه 31 خرداد 1394, 00:10 صبح
توی خط 32 به NewSize یه واحد اضافه کردم دیگه.
حالا با توجه به این مواردی که گفتید ایراد کد من کجاست؟
چرا نمیذاره حافظه ای که برای DestAddr گرفته بودم رو پس بدم؟

ehsan_faal
یک شنبه 31 خرداد 1394, 00:46 صبح
بالاخره بعد از چند ساعت دیباگ کردن برنامه و کلی سرچ فهمیدم مشکل از کجاست.
توی کد قبلی رشته ای که توی هیپ میسازم آخرش کاراکتر پوچ نداره و به همین دلیل وقتی میخوام delete کنم بسته به این که کاراکتر پوچ به طور تصادفی چندتا خونه اونورتر از ته رشته هستش مجبور میشه که یه سری از اطلاعات خود سیستم رو هم حذف کنه که البته ویندوز نمیذاره و ارور میده.
کد درست اینه:

#include <iostream>
#include <string>
#include <assert.h>
using namespace std;
char *reverseCat(const char*, const char*);
void main()
{
char S1[] = "C++ Language is the most powerful ";
char S2[] = ".egaugnaL";
char *tmp = reverseCat(S1, S2);
cout << tmp << endl;
cout << S1 << endl;
cout << S2 << endl;
delete[] tmp;
tmp = 0;
}
char *reverseCat(const char *Dest, const char *Source)
{
int i = strlen(Source);
char *tmpSource = _strdup(Source);
for (int j = 0, x = (i - 1); j < i; j++, x--)
tmpSource[j] = Source[x];
int k = strlen(Source) + strlen(Dest) + 1;
char *tmpDest = new char[k];
assert(tmpDest != 0);
memset(tmpDest, '@', k - 2);
tmpDest[k-1] = 0;
strncpy_s(tmpDest, k, Dest, strlen(Dest));
tmpDest[strlen(Dest)] = 0;
strcat_s(tmpDest, k, tmpSource);
delete[] tmpSource;
return tmpDest;
}

pbm_soy
یک شنبه 31 خرداد 1394, 01:28 صبح
اول از همه تا اونجائی که امکان دارد متغیرها و اشیاء را بصورت محلی تعریف کنید تا در زمان خروج از آن فضای حافظه های گرفته شده آزاد شوند حتی در برخی موارد میتوانید خودتان یک بلوک ساده ایجاد کنید
بلوکها معمولا در قالب یک تابع یا در قالب دستور if , for , while ایجاد میشوند ولی شما میتوانید بدون بااستفاده از {} یم بلوک ایجاد کنید و متغیرها و دستوراتی که از آن متغیرها استفاده میکنند را در آن قرار دهید پس از پایان آن متغیرها نیز از بین میروند

مورد دیگر شما میتوانید memory leak برنامه را توسط خود برنامه و کامپایلر کنتر کنید بصورت زیر (این بخش اصلی یکی از برنامه های نوشته شده من است و چون فرصت نبود یک مثال بنویسم این بخش برنامه را اینجا گذاشتم)


#include<iostream>
#include<conio.h>
#include"paricipatorregister.h"
#include <stdlib.h>

#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>

void main(void)
{
//===== Extra Block to avoiding memory leak.
//===== all variables and memory allocations will be destroy after finish running block
{
ParticipatorRegister p;
int choice=0;
while(choice != 10)
{
system("cls");
cout<<"1)Add Participator"<<endl;
cout<<"2)List Participator"<<endl;
cout<<"3)Search Participator"<<endl;
cout<<"4)Remove Participator"<<endl;
cout<<"5)Edit Participator"<<endl;
cout<<"6)List Paid Participator"<<endl;
cout<<"7)List Not Paid Participator"<<endl;
cout<<"8)Save To File"<<endl;
cout<<"9)Load From File"<<endl;
cout<<"10)Exit"<<endl;
cin>>choice;
system("cls");
switch(choice)
{
case 1:p.addParticipator();break;
case 2:p.listParticipator();break;
case 3:p.searchParticipator();break;
case 4:p.removeParticipator();break;
case 5:p.editParticipator();break;
case 6:p.listPaidParticipator();break;
case 7:p.listNotPaidParticipator();break;
case 8:p.saveAllParticipator();break;
case 9:p.readAllParticipator();break;
case 10:break;
}//switch
}//while
}//===== Extra Block to avoiding memory leak.

_CrtDumpMemoryLeaks(); //for display memory leaks in output window
getch();getch();
}//main



به include اول برنامه توجه کنید

#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>


همینطور به تابع _crtDumpMemoryLeaks در انتهای برنامه توجه کنید که باعث میشود گزارشی کامل از نشت حافظه برنامه در پنجره output یا دیباگ محیط IDE نمایش میدهد این برنامه را در محیط Visual studio 2008 .net نوشته بودم و مشکلی نداشت البته این برنامه کار نخواهد کرد چون هدرفایلی که خودم نوشتم را اینجا نگذاشتم بخاطر حجم زیاد و دیگر موارد

ehsan_faal
یک شنبه 31 خرداد 1394, 02:06 صبح
استفاده از بلوکهایی که گفتید روی متغیرهایی که فضایی رو new میکنند که تاثیری نداره، اونا در هر صورت باید delete بشن دیگه،درسته؟
سوال آخرم هم اینکه اگه ما یه رشته رو از هیپ بگیریم وقتی بخوایم delete کنیم از روی کاراکتر پوچ انتهای رشته میفهمه که چقدر حافظه گرفته شده و الان باید پسش بده ولی اگه یه آرایه از int داشته باشیم چطور؟
ما توی دستور delete فقط آدرس خونه اول رو میدیم بهش دیگه،چجوری تشخیص میده که چندتا بلوک از حافظه رو باید پس بده به حافظه هیپ؟

rahnema1
یک شنبه 31 خرداد 1394, 18:09 عصر
سلام
ببینید معمولا رزرو حافظه به این صورته که new فضای مورد نظر را اشغال می کنه همچنین سایز اون فضای اشغال شده هم در جایی ذخیره می شه
مثلا ممکنه قبل از شروع اون قسمت حافظه سایز اون هم ذخیره بشه که عملگر delete اینجوری تشخیص میده که چه قدر حافظه باید آزاد بشه.
کاراکتر پوچ تاثیری روی این قضیه نداره. مثلا من یک فضا به میزان 10 تا اشغال کنم و اولین مقدار را پوچ بذارم باز هم همون 10 تا هنگام delete آزاد می شه
یک نکته بیشتر تابعهایی که استفاده کردید در زبان c استفاده می شه شما اینجا می تونستید از کلاس std::string و عملیاتهای مربوط به اون استفاده کنید که هم کار باهاش راحته هم گرفتار مدیریت حافظه و کاراکتر پوچ و طول رشته بر اساس strlen و ... نشید چون بحث مدیرت حافظه تو اون کلاس کاملا رعایت شده
تابع strlen طول رشته را بدون کاراکتر پوچ در نظر می گیره
معمولا توصیه می شه در یک تابع وقتی فضایی رزرو می شه در همون تابع هم حافظه آزاد بشه اما اگه واقعا موردی باشه که لازمه تابع حافظه heap را بر گردونه بعدا در یک تابع دیگه باید اون را delete کنید
حالا پیشنهاد می کنم یه بار دیگه همین را با string بنویسید و از هیچ کدام از تابعها و عملگرهایی نظیر strlen و strdup و new و strncpy و strcat و همچنین از آرایه استفاده نکنید ببینید چه طور از آب در میاد :)
علتش اینه که هم خوانایی و تمیزی کد بیشتر می شه و هم اشتباهات به حداقل می رسه

ehsan_faal
یک شنبه 31 خرداد 1394, 19:02 عصر
سلام
ببینید معمولا رزرو حافظه به این صورته که new فضای مورد نظر را اشغال می کنه همچنین سایز اون فضای اشغال شده هم در جایی ذخیره می شه
مثلا ممکنه قبل از شروع اون قسمت حافظه سایز اون هم ذخیره بشه که عملگر delete اینجوری تشخیص میده که چه قدر حافظه باید آزاد بشه.
کاراکتر پوچ تاثیری روی این قضیه نداره. مثلا من یک فضا به میزان 10 تا اشغال کنم و اولین مقدار را پوچ بذارم باز هم همون 10 تا هنگام delete آزاد می شه
یک نکته بیشتر تابعهایی که استفاده کردید در زبان c استفاده می شه شما اینجا می تونستید از کلاس std::string و عملیاتهای مربوط به اون استفاده کنید که هم کار باهاش راحته هم گرفتار مدیریت حافظه و کاراکتر پوچ و طول رشته بر اساس strlen و ... نشید چون بحث مدیرت حافظه تو اون کلاس کاملا رعایت شده
تابع strlen طول رشته را بدون کاراکتر پوچ در نظر می گیره
معمولا توصیه می شه در یک تابع وقتی فضایی رزرو می شه در همون تابع هم حافظه آزاد بشه اما اگه واقعا موردی باشه که لازمه تابع حافظه heap را بر گردونه بعدا در یک تابع دیگه باید اون را delete کنید
حالا پیشنهاد می کنم یه بار دیگه همین را با string بنویسید و از هیچ کدام از تابعها و عملگرهایی نظیر strlen و strdup و new و strncpy و strcat و همچنین از آرایه استفاده نکنید ببینید چه طور از آب در میاد :)
علتش اینه که هم خوانایی و تمیزی کد بیشتر می شه و هم اشتباهات به حداقل می رسه

ممنون از پاسختون.فقط یه مشکل باقی میمونه:
اگه کاراکتر پوچ تاثیری نداره توی عملکرد delete پس چرا توی پست اول موقع delete کردن حافظه ای که توی تابع میگرفتیم ارور میداد ولی بعد از اینکه کارکتر پوچ رو گذاشتم اون ارور رفع شد؟
(البته خیلی مطمئن نیستم به خاطر کاراکتر پوچ باشه چون واقعا نمیدونم به غیر از پیمایش تک تک کاراکترها راهی هست که بشه فهمید که آیا فلان آرایه کارکترها با پوچ ختم شده یا نه)


در مورد استفاده از string هم حق با شماست خیلی راحت تر از استفاده از اشاره گر ها و کاراکترهاست:

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
void main()
{
string s1("C++‎‎ Language is the most powerful ");
string s2(".egaugnaL");
reverse(s2.begin(), s2.end());
s1.append(s2);
cout << s1 << endl;
getchar();
}

rahnema1
یک شنبه 31 خرداد 1394, 20:38 عصر
اینجا در کد شما دسترسی خارج از محدوده آرایه انجام می شد
DestAddr[NewSize - 1] = 0;