اشارهگر (علوم رایانه)
در فرهنگ علوم رایانه متغیرهای از نوع اشارهگر (به انگلیسی: Pointer)، به متغیرهایی گفته میشود که محتوای آنها، آدرس خانهای از حافظه یا نیل[1] است. در عمل، اشارهگر متغیری است که به متغیر دیگری اشاره میکند.
اشارهگرها از پرکاربردترین نوع متغیرها در زبانهای برنامهسازی محسوب میشوند. این امر به این علت است که اشارهگرها، گاهی اوقات تنها راه بیان محاسبهای مشخص هستند و بخشی دیگر به این علت است که معمولاً باعث فشردگی و کارایی بیشتر قطعه برنامهها نسبت به ایجاد آنها با روشهای دیگر میشود.
اشارهگرها در زبانهای برنامهنویسی مختلف
زبان سی
در زبان سی اشارهگرها یکی از اجزای پایه زبان هستند و ارتباط اساسی با آرایه، ساختارها، و توابع دارند. چندین عملگر مختلف برای کار بر روی اشارهگرها وجود دارد که مهمترین آنها عملگرهای یکانی * و & هستند. عملگر & (عملگر آدرس) آدرس عملوند خود را برمیگردد. عملگر * (عملگر در آدرس) هم محتوای خانهای که آدرس آن عملوندش قرار دارد را در دسترس میسازد. برای تعریف یک اشاره گر باید قبل از نام آن علامت * قرار گیرد.
int x = 1, y = 2;
int *ip;
ip = &x;//آدرس متغیر x درون اشاره گر ip قرار میگیرد
y = *ip;//محتوای متغبر x به کمک اشاره گر ip درون متغیر y قرار میگیرد
y = 1;
در مثال بالا ip یک اشاره گر به عدد صحیح است، عبارت ip = &x آدرس متغیر x را در ip قرار میدهند. y = *ip هم محتوای آدرسی که در ip قرار دارد را به متغیر y منتسب میکند. به کمک اشاره گرها میتوان از حافظه رایانه به صورت پویا استفاده کرد. بدین صورت که هر وقت احتیاج به حافظه داشتیم آن را به برنامه اختصاص میدهیم و هر وقت که کارمان تمام شد آن را به سیستم پس میدهیم. دو توابع کتابخانهای به نام malloc و free برای انجام این کار وجود دارند. تابع malloc که بدین صورت اعلان شده:
void *
malloc(size_t size);
به اندازه size بایت از سیستم فضا گرفته و اشاره گری به ابتدای این مکان را برمیگرداند. تابع free که بدین صورت اعلان شده:
void
free(void *ptr);
حافظه ای که آدرس اولین بایت آن در ptr قرار دارد را به سیستم برمیگرداند.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *x;
x = malloc(sizeof(int));
*x = 12;
printf("x is: %d\n", *x);
free(x);
return 0;
}
اشاره گرها و فراخوانی توابع
در زبان سی، وقتی که متغیری را به عنوان آرگومان برای تابع ارسال میکنیم، در حقیقت مقدار آن متغیر در پارامتر تابع کپی میشود و کلیه عملیات بر روی آن کپی انجام میشود و متغیر اصلی بدون تغییر باقی میماند. این کار به کمک یک پشته انجام میشود. برای اینکه تابع قادر باشد تا بر روی آرگومانها تغییر ایجاد کند، باید به جای مقدار، آدرس آرگومان را ارسال کنیم. چرا که به کمک آدرس تابع میتواند مستقیماً به سراغ حافظه رفته و محتوای آن را تغییر دهد. برای اینکه اشاره گری به تابع ارسال شود، پارامترهای تابع باید از نوع اشاره گر تعریف شوند. تابع زیر محتوای دو متغیر را با یکدیگر تعویض میکند.
void swap(int *x, int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
اشاره گرها و آرایهها
در زبان سی نام یک آرایه، اشاره گری به اولین عنصر آرایه است. برای دستیابی به بقیه عناصر آرایه، شماره اندیس آن خانه با آدرس اولین خانه جمع میشود. (چون خانههای آرایه به صورت پشت سر هم در حافظه ذخیره میشوند)
int a[10];
int *ip;
ip = a;
ip[1] = 12;
*(ip+2) = 67;
در مثال بالا ip = a مقدار a (که اشاره گری به یک عدد صحیح است) را به اشاره گر ip انتساب میدهد؛ بنابراین از این به بعد میتوان از طریق اشاره گر ip هم به محتویات آرایه دسترسی داشت. ip[1] = ۱۲ عدد ۱۲ را به دومین عنصر آرایه (چون در سی اندیس آرایهها از صفر شروع میشود) انتساب میدهد.
در خط آخر، ابتدا ip (که آدرس یک خانه را دربردارد و آدرس هم یک عدد است) با ۲ جمع شده و سپس یک آدرس جدید به دست میآید که همان عنصر سوم آرایه است. سپس عدد ۶۷ در این آدرس قرار میگیرد.
اشاره گرها و توابع
همانند آرایه، نام یک تابع هم یک اشاره گر است. در زبان سی، نام یک تابع به ابتدای محلی از حافظه اشاره میکند که کدهای تابع در آنجا قرار دارند. در هنگام کار با اشارهگر به توابع بهتر است برای بالا بردن خوانایی از typedefها استفاده کنید.
typedef void (*func_t) (int*, int*);
اعلان بالا، نوع داده جدیدی به نام func_t تعریف میکند. این نوع داده یک اشاره گر به تابع است. اما فقط میتواند به توابعی اشاره کند که دو آرگومان از نوع int* دریافت میکنند و هیچ چیزی برنمیگردانند. (مانند تابع swap که قبلاً تعریف کردیم)
#include <stdio.h>
void swap(int*, int*);
typedef void (*func_t) (int*, int*);
int main(void)
{
int a = 10, b = 20;
func_t exchg;
exchg = &swap;
exchg(&a, &b);
(void)printf("a = %d\nb = %d\n", a, b);
return 0;
}
void swap(int *x, int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
دستور func_t exchg یک اشاره گر به تابع به نام exchg ایجاد میکند که exchg میتواند به توابعی که دو اشاره گر به عدد صحیح میگیرند و هیچ چیز برنمیگردانند اشاره کند. دستور exchg = &swap آدرس اشاره گر swap را در exchg قرار میدهد. حالا که آدرس تابع در اشاره گر exchg در دسترس است، میتوان تابع را از طریق این اشاره گر فراخوانی کرد. اشاره گر به توابع را میتوان به عنوان آرگومان به توابع دیگر ارسال کرد. همچنین میتوان آنها را در آرایه ذخیره کرد. مثلاً در کتابخانه استاندارد سی تابعی به نام bsearch وجود دارد که عمل جستجوی دودویی را انجام میدهد. این تابع بدین شکل اعلان شدهاست:
void *
bsearch(const void *key, const void *base, size_t nmemb, size_t size,
int (*compar) (const void *, const void *));
پارامتر آخر این تابع، اشاره گر به تابعی است که یک عدد صحیح برمیگرداند و دو آرگومان از نوع اشاره گر دریافت میکند. اشاره گرها از نوع void تعریف شدهاند تا بتوانند به هر نوع دادهای اشاره کنند.
مشکلات اشاره گرها
اشاره گرهایی هنوز مقدار دهی نشدهاند و به جایی اشاره نمیکنند میتوانند برای برنامه خطرناک باشند. به مثال زیر توجه کنید:
int *x;
*x = 12; /* Error */
اشاره گر x هنوز مقدار دهی نشده و به یک خانه تصادفی اشاره میکند؛ بنابراین دستور دوم سعی میکند عدد ۱۲ را در یک جای تصادفی از حافظه قرار دهد. اگر خوش شانش باشید، این خانه به جایی در خارج از محدوده آدرس دهی برنامه اشاره میکند. چون در این صورت سیستمعامل اجازه دسترسی به آن خانه را نداده و پیغام segmentation fault تولید میکند. اما اگر بد شانس باشید و آن خانه در محدوده آدرس دهی برنامه باشد، محتوای یکی از ساختمان داده برنامه خود را بازنویسی کردهاید. اطلاعات آسیب خواهند دید.
پانوشتهها
- nil
منابع
- مفاهیم مربوط به زبانهای برنامهنویسی (انگلیسی)
سیپلاسپلاس: چگونه آن را برنامهنویسی کنیم (چاپ ششم) (انگلیسی)