اشاره‌گرهای معلق

اشاره‌گرهای معلق در زبانهای برنامه نویسی به یک شئ نامشخص اشاره می‌کند که سلامت کد را به خطر می‌اندازد.

اشاره‌گر معلق زمانی به وجود می‌آید که یک شی حذف یا آزاد شود، بدون اینکه مقدار اشاره‌گر تغییر شده باشد. در نتیجه آن اشاره‌گر به مکانی اشاره می‌کند که دیگر وجود ندارد. حتی ممکن است سیستم‌عامل مکان قبلی حافظه که آزاد شده بود را به فرآیند دیگری داده باشد، در این صورت آن اشاره‌گر به دادهای متفاوت اشاره می‌کند.

دلایل بروز اشاره‌گرهای معلق 

در بسیاری از زبان های برنامه نویسی، مانند سی، وقتی یک شئ از محدوده خارج می‌شود یا صریحاً دستور آزاد شدن دریافت می‌کند، اشاره‌گرهایی که به آن شئ اشاره می‌کنند تغییر نمی‌کنند.

به عنوان مثال به کد زیر توجه کنید:
{
   char *dp = NULL;
   /* ... */
   {
       char c;
       dp = &c;
   } /* c falls out of scope */
     /* dp is now a dangling pointer */
}
در صورتی که سیستم عامل بتواند اشاره‌گرهایی که به پیوندهای پوچ می‌رسند را شناسایی کند، می‌تواند آن‌ها را برابر 0 یا NULL قرار دهد. یک راه دیگر این است که به‌طور مثال اجازه داده نشود dp دوباره استفاده شود.

یک راه دیگر به وجود آمدن اشاره‌گر معلق، استفادۀ همزمان از تابع malloc و در ادامه free است. در واقع بعد از اینکه یک اشاره‌گر با malloc مقداردهی می‌شود و سپس توسط free خالی می‌شود، و اشاره گر استفاده شده معلق می‌شود. به کد زیر توجه کنید:
#include <stdlib.h>

void func()
{
    char *dp = malloc(A_CONST);
    /* ... */
    free(dp);         /* dp now becomes a dangling pointer */
    dp = NULL;        /* dp is no longer dangling */
    /* ... */
}
یک اشتباه رایج استفاده از آدرس متغیرهای محلی در یک تابع است که با برگرداندن یک اشاره‌گر به آن قسمت، آن اشاره‌گر را معلق می‌کند؛ چرا که اشاره‌گر استفاده شده به جایی از حافظه اشاره می‌کند که پس از اتمام اجرای تابع، آزاد شده و دیگر تضمینی بر حفظ مقادیر وجود ندارد. 
int *func(void)
{
    int num = 1234;
    /* ... */
    return &num;
}
تلاش برای خواندن مقدار برگشت داده شده از تابع، پس از اندکی بعد از فراخوانی ان تابع ممکن است نتیجه 1234 را برگرداند اما در ادامه برنامه احتمالاً آن حافظه برای استفاده دیگری اختصاص خواهد یافت و مقدار مورد نظر ما، پاک خواهد شد.

دلایل به وجود آمدن اشاره‌گرهای وحشی 

اشاره‌گر وحشی زمانی به وجود می‌آید که اشاره‌گر قبل از اولین استفاده، مقداردهی نشده باشد. در واقع زمانی که یک اشاره‌گر مقداردهی اولیه نشود، آن اشاره‌گر حتماً وحشی خواهد بود. به مثال زیر توجه کنید:

int f(int i)
{
    char *dp;    /* dp is a wild pointer */
    static char *scp;  /* scp is not a wild pointer:
                        * static variables are initialized to 0
                        * at start and retain their values from
                        * the last call afterwards.
                        * Using this feature may be considered bad
                        * style if not commented */
}

سوراخ‌های امنیتی مرتبط 
مانند سرریز بافر، اشاره‌گرهای معلق به راحتی به سوراخ‌های امنیتی تبدیل می‌شوند. برای مثال اگر یک اشاره‌گر بخواهد یک تابع virtual function را فراخوانی کند، در صورتی که اشاره‌گر دستکاری شده باشد، یک آدرس دیگر می‌تواند فراخوانی شود که ممکن است دستور اجرای یک فرآیند خرابکارانه باشد.

روشهای جلوگیری 
در زبان برنامه‌نویسی C/C++ راحت‌ترین روش قابل استفاده، استفاده از یک تابع جایگزین برای free یا استفاده از تابع delete در زمان مناسب است. به هر حال با این روش، تنها برای اشاره‌گرهایی اثربخش است که مستقیماً توسط توابع تعریف شده خالی شوند، اما سایر اشاره‌گرها را از حال معلق خارج نمی‌سازد.

#include <assert.h>
#include <stdlib.h>

/* Alternative version for 'free()' */
void safefree(void **pp)
{
    /* in debug mode, abort if pp is NULL */
    assert(pp);
    if (pp != NULL) {               /* safety check */
        free(*pp);                  /* deallocate chunk, note that free(NULL) is valid */
        *pp = NULL;                 /* reset original pointer */
    }
}

int f(int i)
{
    char *p = NULL, *p2;
    p = (char *)malloc(1000);    /* get a chunk */
    p2 = p;              /* copy the pointer */
    /* use the chunk here */
    safefree((void **)&p);       /* safety freeing; does not affect p2 variable */
    safefree((void **)&p);       /* this second call won't fail */
    char c = *p2;       /* p2 is still a dangling pointer, so this is undefined behavior. */
    return i + c;
}

راه حل دیگر این است که قبل از استفاده از <tt>malloc()<tt> اشاره‌گر بررسی شود و در صورت معلق بودن 0 مقداردهی شود.

    safefree(&p);        /* i'm not sure if chunk has been released */
    p = malloc(1000);    /* allocate now */

این راهکارها می‌تواند توسط یک دستور <tt>#define<tt> با ایجاد چند ماکرو مفید، خلاصه شود. به هر شکل، این برنامه‌نویس است که باید تصمیم بگیرد از توابع امن استفاده کند. در صورت عدم استفاده از این توابع مشکلات اشاره‌گرهای معلق باز هم برمیگردد.

یک روش دیگر که قابل استفاده در راهکارهایی که بشتر ساختاریافته هستند استفاده از اشاره‌گر هوشمند است. اشاره گر هوشمند، به این شکل عمل می‌کند که تعداد اشاره‌گرها به یک شی را میشمارد و زمانی که نیاز به آزاد سازی حافظه شد، با ازاد سازی ان شی، تمام اشاره‌گرهای ان را هم به NULL تغییر می‌دهد. 

روش دیگر استفاده از تکنیک Garbage Collection استف. در این حالت این سیستم عالم است که حساب اشاره‌گرهای برنامه اجرا شده را در اختیار میگیرد و در صورتی که شی پیدا شود که اشاره‌گری به ان اشاره نمی‌کند، ان شی را از حافظه پاک می‌کند. این روش به کلی احتمال بروز اشاره‌گرهای معلق را از بین می‌برد.

در زبانهای برنامه نویسی سطح بالا، مانند JAVA، امکان بروز اشاره‌گر معلق وجود ندارد، چرا که هیچ روش مستقیمی برای خالی کردن حافظه در این زبان تعبیه نشده و تمام آزاد سازی حافظه توسط Garbage Collector انجام می‌شود.

روش شناسایی 
یک روش این است که تمام اشاره‌گرها را تا قبل از مقدار دهی برابر 0 یا null pointer قرار دهیم، همین روش باید برای اشاره‌گرهایی که مقادیر مرتبط با ان آزاد سازی شده‌اند استفاده شود.  اشارهگهر null خطری برای استفاده ندارد و در هیچ سیستم کامپیوتری استفاده از ان منجر به از دستگاری داده ها و خرابکاری نمی‌شود. در بیشتر کامپایلرها با فراخوانی یا خواندن مقدار null pointer برنامه متوقف می‌شود و ادامه نمی‌یابد.

منابع 

اشکال‌های برنامه‌نویسی
سوءاستفاده امنیتی از رایانه