View Full Version : سوال: راه ایمن برای تشخیص اشاره گری که قبلان حذف شده
negative60
یک شنبه 15 آذر 1394, 01:15 صبح
سلام
من دنبال یک راه حل بودم برای اینکه بفهمم آیا یک اشاره گر قبلان حذف شده یا خیر, البته میدونم در اینجور مواقع که چند اشاره گر به یک شی اشاره میکنند باید از Shared Pointer یا smart pointers استفاده کرد اما به دلایلی دنبال راه حل دیگه ای بودم بعد خودم این روش رو درست کردم, توی تست هایی که خودم انجام دادم این روش مشکلی نداشت، اما میخوام بدونم کلا این روش من ایمن هست یا نه و امکان داره این روش در شرایطی نتیجه اشتباه هم داشته باشه؟ مثلاً آدرس اشاره گری که از بين رفته، به يک اشاره گر ديگه اختصاص داده بشه و يا اصلاً آدرس اشاره گر در مموری توسط بافر شعی ديگه اشغال بشه؟
نکته: اين روش در کامايلر GCC و سيستم عامل لينوکس به درستی کار ميکنه اما در MSVC خير
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
string *myObject;
bool isTruePointer(const void *p)
{
if(!p) return false;
unsigned long B = 0;
memcpy(&B, p, sizeof(p));
if(B == 0)
{
return false;
}
return true;
}
int main()
{
myObject = new string;
while(true)
{
printf("press the Enter key...\n");
getchar();
bool res = isTruePointer(myObject);
printf("can i use this pointer?: %s\n", res ? "YES": "NO");
if(res){
delete myObject;
printf("object was deleted.\n");
}
}
return 0;
}
Nader G
یک شنبه 15 آذر 1394, 02:00 صبح
1. نه ایمن نیست چون تضمینی وجود نداره که عملگر delete مقدار اشاره گر رو 0 کنه.
2. در کدی که نوشتید باگ وجود داره: سایز اشاره گر و unsigned long ممکنه برابر نباشن، یعنی مثلا اشاره گر 8 بایت باشه و unsigned long چهار بایت.
negative60
یک شنبه 15 آذر 1394, 02:14 صبح
1. نه ایمن نیست چون تضمینی وجود نداره که عملگر delete مقدار اشاره گر رو 0 کنه.
2. در کدی که نوشتید باگ وجود داره: سایز اشاره گر و unsigned long ممکنه برابر نباشن، یعنی مثلا اشاره گر 8 بایت باشه و unsigned long چهار بایت.
در سيستم های 32 بيتی اشاره گر هم 4 بايت هست
printf("szie: %d\n", sizeof(p));
Nader G
یک شنبه 15 آذر 1394, 02:57 صبح
من گفتم ممکنه برابر نباشن. در ضمن شما جایی در کدتون 32 یا 64 بیتی بودن رو چک نکردید و البته در مورد سایز unsigned long هم تضمین 4 بایت بودن وجود نداره.
وقتی اشاره گری رو delete می کنید آدرسی که در اون ذخیره شده دیگه معتبر نیست و دیگه نمی تونید از اون آدرس استفاده کنید اما مقدار خود اشاره گر ممکنه
تغییر نکنه که معمولا هم نمیکنه. برای همین بعضی ها بلافاصله بعد از delete مقدار اشاره گر رو صفر می کنن.
delete ptr;
ptr = 0;
کد در این خط یه مشکل دیگه هم داره:
memcpy(&B, p, sizeof(p));
از آدرسی که p بهش اشاره می کنه (و معتبر نیست) به اندازه (sizeof(p در آدرس متغیر B کپی میکنه. چرا؟ ممکنه حافظه اختصاص داده شده از این مقدار کمتر یا
بیشتر باشه و مقادیری که در اون قسمت حافظه ذخیره شدن هم هر چیزی باشن.
negative60
یک شنبه 15 آذر 1394, 04:36 صبح
من گفتم ممکنه برابر نباشن. در ضمن شما جایی در کدتون 32 یا 64 بیتی بودن رو چک نکردید و البته در مورد سایز unsigned long هم تضمین 4 بایت بودن وجود نداره.
اندازه سایز اشاره گر و نوع Long به معماری ۳۲ و ۶۴ مربوطه که در ۳۲ بیتی هر دو چهار و در ۶۴ هردو ۸ هست, طبق کدام استاندارد نوع long در ۳۲ بیت میتونه ۸ بایت داشته باشه؟
وقتی اشاره گری رو delete می کنید آدرسی که در اون ذخیره شده دیگه معتبر نیست و دیگه نمی تونید از اون آدرس استفاده کنید اما مقدار خود اشاره گر ممکنه
تغییر نکنه که معمولا هم نمیکنه. برای همین بعضی ها بلافاصله بعد از delete مقدار اشاره گر رو صفر می کنن.
1
2
delete ptr;
ptr = 0;
ببینید عرض کردم "در اینجور مواقع که چند اشاره گر به یک شی اشاره میکنند"
شما وقتی اشاره گر رو نال میکنید شیٔ نال نمیشه و مقدار دیگر اشاره گرها ثابت میمونه اگه اینطور بود که دیگه ما مشکلی نداشتیم با یه مقایسه میتونستیم متوجه بشیم شیٔ قبلان پاک شده یا خیر
کد در این خط یه مشکل دیگه هم داره:
1
memcpy(&B, p, sizeof(p));
از آدرسی که p بهش اشاره می کنه (و معتبر نیست) به اندازه (sizeof(p در آدرس متغیر B کپی میکنه. چرا؟ ممکنه حافظه اختصاص داده شده از این مقدار کمتر یا
بیشتر باشه و مقادیری که در اون قسمت حافظه ذخیره شدن هم هر چیزی باشن.
الان من تعجبم از همینه که چرا در لینوکس بعد اینکه شی رو پا کردیم وقتی که آدرس شی که پاک شده رو کپی میکنیم مقدار ۰ هست و چرا فضایی که آزاد کردیم به اشیای دیگه که new میکنیم تعلق نمیگیره؟
تو این روش با استفاده از این موضوع هست که میشه فهمید اشاره گر قبلان پاک شده یا خیر!
negative60
یک شنبه 15 آذر 1394, 06:08 صبح
بعد از اینکه مقدار بیشتری رو new کردم, فضای اختصاص داده شده به اشاره گر قبلی به فضای جدید اختصاص داده شد, و همانطور که مشخص بود این روش درستی برای چک کردن اشاره گرها نیست.
Ananas
یک شنبه 15 آذر 1394, 09:36 صبح
سلام.
این تابع میتونه بگه که چه مقدار فضا در این آدرس وجود داره:
_msize
در فایل malloc.h هست. اگر مقدار صفر رو برگردونه یعنی اشاره گر آزاد شده. پس نا معتبره.
Ananas
یک شنبه 15 آذر 1394, 10:06 صبح
در مورد کلاس ها هم اگر میخواید از smart pointer استفاده نکنید من یک کلاس تعریف کردم که مشابه اشیا COM عمل میکنه. میتونید ازش استفاده کنید. فقط باید با متد Release آزاد بشه نه delete:
/***********************************/
/* */
/* Besmellaherrahmanerraheam */
/* */
/* file: mq_refclass.h */
/* */
/* MHD_1392. */
/* */
/***********************************/
#ifndef __MQ_REFCLASS_H__DEFINED
#define __MQ_REFCLASS_H__DEFINED
//------------------------------------------------------------------------------
#define VERSION_OF_MQ_REFCLASS 139212081144UI64
//------------------------------------------------------------------------------
#ifndef MQR_NULL
# ifdef __cplusplus
# define MQR_NULL 0
# else
# define MQR_NULL ((void *)0)
# endif
#endif
typedef class TMQReference // TMQR_ tmqr_
{
private:
int __RefCount;
protected:
virtual ~TMQReference() { __RefCount = 0; };
public:
TMQReference() { __RefCount = 1; };
int AddRef() { __RefCount++; return __RefCount; };
int Release() { __RefCount--;
if ( __RefCount <= 0 )
{ delete this; };
return __RefCount; };
} * PMQReference, MQ_REFERENCE, * PMQ_REFERENCE;
int MQR_ReleaseAndNull_void(void ** ppObj)
{
if (ppObj != MQR_NULL)
{
if (*ppObj != MQR_NULL)
{
int Result = ((TMQReference *)*ppObj)->Release();
*ppObj = MQR_NULL;
return Result;
};
};
return 0;
};
int MQR_Instance_void(void ** ppOut, void * pObj)
{
if (ppOut != MQR_NULL)
{
*ppOut = (void *)pObj;
if (pObj != MQR_NULL)
{
return ((TMQReference *)pObj)->AddRef();
};
};
return 0;
};
int MQR_SetNewInstance_void(void ** ppInOut, void * pObj)
{
MQR_ReleaseAndNull_void (ppInOut);
return MQR_Instance_void(ppInOut, pObj);
};
#define MQR_ReleaseAndNull( pObj ) MQR_ReleaseAndNull_void( (void **)&pObj );
#define MQR_Instance( ppOutObj, pObj ) MQR_Instance_void ((void **)ppOutObj, (void * ) pObj );
#define MQR_SetNewInstance( ppOutObj, pObj ) MQR_SetNewInstance_void((void **)ppOutObj, (void * ) pObj );
//------------------------------------------------------------------------------
/************************************************** *****************************
* Example:
*
* typedef class TMQR_Object: public TMQReference
* {
* protected:
* virtual ~TMQR_Object() { ... };
*
* public:
* TMQR_Object() { ... };
*
* } * PMQR_Object;
*
************************************************** *****************************/
//------------------------------------------------------------------------------
#endif // __MQ_REFCLASS_H__DEFINED
و یک نمونه از این کلاس:
typedef class TMQR_Vector: public TMQReference
{
private:
float * __p_Floats;
int __Count;
protected:
virtual ~TMQR_Vector()
{
free(__p_Floats);
__p_Floats = NULL;
__Count = 0;
};
public:
TMQR_Vector(const int count)
{
__Count = count;
__p_Floats = (float *)malloc(sizeof(float) * count);
};
int GetCount() {return __Count; };
float GetI(const int index)
{
if ((index >= 0) && (index < __Count))
return __p_Floats[index];
else
return 0.0f;
};
bool SetI(const int index, const float val)
{
if ((index >= 0) && (index < __Count))
{
__p_Floats[index] = val;
return true;
};
return false;
};
} * PMQR_Vector;
قرارداد من اینه که اسم کلاس هایی که از TMQReference مشتق میشن با عبارت TMQR_ شروع بشن. و برای انتساب و آزاد سازی از مکروهای MQR_ReleaseAndNull و MQR_Instance و MQR_SetNewInstance استفاده بشه.
متد مخرب کلاس هم باید در کلاسهای مشتق شده به همین شکل باشه.(مثل کلاس وکتور که تعریف کردم.)
و جدای از این کلاس، یک نکته ی مهم در مورد آزاد کردن کلاسها که من روش تاکید دارم این هست که اشاره گرها و مقادیر درونیه کلاس همه صفر بشن که در استفاده های بعدی حتی اگر دسترسی ممنوع هم انجام شد مشکلی پیش نیاد. مثلا __p_Floats و __Count که در کلاس وکتورم تعریف کردم.
rahnema1
یک شنبه 15 آذر 1394, 19:11 عصر
سلام
این هم یه رقم دیگه وقتی اشاره گر به آرایه نباشه
#include <string>
#include <iostream>
template <typename Type>
struct smart_ptr {
smart_ptr(Type* buffer, bool valid):buffer(buffer), valid(valid)
{}
smart_ptr(Type* buffer):smart_ptr<Type>(buffer, true)
{}
smart_ptr():smart_ptr<Type>(nullptr,false)
{}
smart_ptr(const smart_ptr<Type>& other) = delete;
smart_ptr<Type>& operator=(const smart_ptr<Type>& other) =delete;
void alloc(Type* other_buffer) {
if(!this->valid) {
this->buffer = other_buffer;
this->valid = true;
}
}
void free() {
if(this->valid) {
delete this->buffer;
this->valid = false;
}
}
~smart_ptr() {
free();
}
Type& operator* () {
return *buffer;
}
Type* operator-> () {
return buffer;
}
bool isValid() {
return valid;
}
private:
Type* buffer;
bool valid;
};
int main()
{
smart_ptr<std::string> ptr (new std::string("Hello"));
std::cout << *ptr << std::endl;;
std::cout << ptr.isValid() << std::endl;
ptr.free();
std::cout << ptr.isValid() << std::endl;
ptr.alloc(new std::string("salam"));
std::cout << ptr.isValid() << std::endl;
}
negative60
یک شنبه 15 آذر 1394, 20:14 عصر
تشکر بابت کمک،
بيشتر دنبال راه حلی بودم که با استفاده از اون بشه چک کرد که آيا يک اشاره گر قابل استفاده هست يا نه که دوستان تابع _msize رو معرفی کردن که معادلش
در لينوکس malloc_usable_size هست .
باز هم مرسی بابت کمک
rahnema1
یک شنبه 15 آذر 1394, 21:06 عصر
ببینید مثلا توی ویندوز هم HeapValidate یا HeapSize داریم اما ضمانتی نیست که مثلا فضایی که توسط عملگر new در کتابخانه استاندارد اشغال شده باشه با این تابعها سازگار باشه یعنی کامپایلر به کامپایلر و کتابخونه به کتابخونه ممکنه جوابها متفاوت بشه
حتی توی استاندارد گفته مثلا هنگام اجرای new و delete لزوما تابعهایی مثل malloc و free ممکنه صدا زده نشوند
Nader G
یک شنبه 15 آذر 1394, 22:59 عصر
تمام این توابعی که نام میبرید به یک پوینتر معتبر به عنوان پارامتر احتیاج دارن و نمیشه از آدرس حافظه ای که قبلا آزاد شده استفاده کرد.
rahnema1
یک شنبه 15 آذر 1394, 23:19 عصر
کدوم تابعها منظورتونه؟
Nader G
یک شنبه 15 آذر 1394, 23:37 عصر
msize, malloc_usable_size, HeapSize, HeapValidate_
البته این توابع بیشتر برای دیباگ استفاده میشن و از این جهت برای پیدا کردن باگ ها میتونن کمک کنن ولی نه در روال عادی برنامه برای تشخیص
اینکه حافظه مون قبلا آزاد شده یا نه.
rahnema1
یک شنبه 15 آذر 1394, 23:59 عصر
ببنید من با این روشها موافق نیستم و توضیح دادم که چرا نباید استفاده بشه ضمن اینکه این smart_ptr را هم واسه همین گذاشتم به عنوان روش مطمئن
اما مثلا در توضیحات HeapValidate (https://msdn.microsoft.com/en-us/library/windows/desktop/aa366708(v=vs.85).aspx) مراجعه کنید مطالب زیر می گه که می شه استفاده کرد یعنی در مورد حافظه free شده استفاده بشه FALSE برمی گردونه ولی اگر حافظه valid بود مقدار غیر صفر بر می گردونه
Nader G
دوشنبه 16 آذر 1394, 00:01 صبح
سلام
این هم یه رقم دیگه وقتی اشاره گر به آرایه نباشه
در copy constructor مقدار متغیر valid چی هست وقتی که تستش می کنید؟
rahnema1
دوشنبه 16 آذر 1394, 00:04 صبح
منظورم این بوده که اگر valid نبود یعنی اگر بافر خالی بود بیاد اشاره گر باصطلاح هوشمند دیگه را کپی کنه در غیر این صورت نشت حافظه پیش میاد
Nader G
دوشنبه 16 آذر 1394, 00:17 صبح
منظورم اینه که ظاهرا متغیر قبل از استفاده initialize نشده.
مشکل دیگه ای که به وجود میاد این نیست که بعد از اجرای copy constructor حالا آدرس بافر در دو smart pointer ذخیره شده؟
Nader G
دوشنبه 16 آذر 1394, 00:37 صبح
ببنید من با این روشها موافق نیستم و توضیح دادم که چرا نباید استفاده بشه ضمن اینکه این smart_ptr را هم واسه همین گذاشتم به عنوان روش مطمئن
اما مثلا در توضیحات HeapValidate (https://msdn.microsoft.com/en-us/library/windows/desktop/aa366708(v=vs.85).aspx) مراجعه کنید مطالب زیر می گه که می شه استفاده کرد یعنی در مورد حافظه free شده استفاده بشه FALSE برمی گردونه ولی اگر حافظه valid بود مقدار غیر صفر بر می گردونه
بله در مورد HeapValidate درست می فرمایید. البته HeapValidate در حالت دیباگ قبل از برگرداندن FALSE یک breakpoint ایجاد میکنه و اجرای برنامه رو متوقف میکنه.
rahnema1
دوشنبه 16 آذر 1394, 01:27 صبح
منظورم اینه که ظاهرا متغیر قبل از استفاده initialize نشده.
مشکل دیگه ای که به وجود میاد این نیست که بعد از اجرای copy constructor حالا آدرس بافر در دو smart pointer ذخیره شده؟
درسته اون شرط را نباید در copy constructor چک می کردم
این که تو تا ذخیره شده هم درسته بنابراین هم عملگر انتساب و هم copy راحذف کردم چون توی این کاربرد فقط می خواد بدونه که مقدار اشاره گر استفاده شده یا نه و چیزهای بیشتر اضافیه و اشکال ایجاد می کنه یه سری تغییر دادم
باز هم اگه اشکال بود گوشزد کنید
Nader G
دوشنبه 16 آذر 1394, 07:13 صبح
بهتره در تابع alloc حافظه فعلی آزاد بشه و اشاره گر جدید ذخیره بشه. وگرنه ممکنه نشت حافظه پیش بیاد.
چون اگه قرار باشه حتما یادمون باشه قبلش تابع free رو صدا بزنیم که دیگه اشاره گرمون خیلی هم smart نیست.
rahnema1
سه شنبه 17 آذر 1394, 00:23 صبح
اینم نکته خوبیه
حتی مثلا می شه isValid را با همون alloc ترکیب کرد
حالا در حدی که بتونه تشخیص بده قبلا مورد استفاده بوده یا نه، هوشمنده. حتی اونقدر هوشمنده که می تونه وقتی از scope خارج شد free بشه!
فعلا همین قدر کافیه هر کسی خواست خودش بیاد گسترش بده
vBulletin® v4.2.5, Copyright ©2000-1404, Jelsoft Enterprises Ltd.