جلوگیری از integer overflow

هر نوع عددی در زبان ‪C++‬ دارای محدوده خاصی است، بطور مثال برای نوع short int اعداد می‌توانند در بازه‌ای بین ‪-32768‬ و 32767 ذخیره شوند، ولی اگر عددی از این بازه فراتر رفت؟ در اینحالت integer overflow رخ خواهد داد. بطور مثال:

short int x = 32767;
x = x + 1; //x = -32768

با توجه به این مثال، مقدار x حداکثر مقداری است که نوع short int می‌تواند ذخیره کند، اگر عدد ۱ را به این مقدار اضافه کنیم از بازه قابل قبول فراتر خواهد رفت و integer overflow رخ خواهد داد. در نتیجه مقدار x تغییر علامت داده و برابر منفی 32768 می‌گردد. برای جلوگیری از چنین تغییر علامت‌های ناگهانی و برای جلوگیری از محاسبات غلط، باید از integer overflow جلوگیری نمود. روش‌های متفاوتی برای جلوگیری از integer overflow می‌تواند مورد استفاده قرار گیرد.

از یک تابع یا کلاس کمکی استفاده کنید. تابعی ایجاد کنید تا قبل از محاسبات بررسی کند که آیا integer overflow صورت می‌گیرد یا خیر. بطور مثال تابع is_safe_add این کار را انجام می‌دهد:

short int x = 32767, y = 32767, z;
if(is_safe_add(x, y)) z = x + y;

تابع is_safe_add بررسی می‌کند که آیا نتیجهٔ جمع دو عدد x و y بدون integer overflow خواهد بود یا خیر. بنابراین در مثال قبل، هیچگاه عمل جمع صورت نخواهد گرفت زیرا این تابع، مقدار false را برخواهد گرداند. برای پیاده‌سازی تابع is_safe_add می‌توانید از چند شرط ساده برای کنترل بازه‌های اعداد x و y استفاده کنید. به جای ایجاد تابعی به نام is_safe_add می‌توانید یک کلاس جدید برای کار با نوع short int ایجاد کنید:

ShortInt x = 32767;
x = x + 1; //throw an exception

در این نمونه مثال، «عملگر +» در کلاس ShortInt سربارگذاری و پیاده‌سازی شده است تا در صورتیکه integer overflow رخ می‌دهد، یک استثناء برای توقف اجرای کدها، صادر گردد.

مقادیری که در متغیر ذخیره می‌شوند را محدود کنید. ورودی‌های کاربر را محدود کنید، تا زمانیکه مقادیر بزرگی در متغیرها ذخیره نشوند، احتمال رخ‌دادن integer overflow ممکن نیست. این روش یک مزیت نسبت به روش قبل دارد و آن این است که قبل از هر محاسبه‌ای، نیاز به بررسی احتمال وقوع integer overflow نیست. بنابراین سرعت محاسبات در این روش بیشتر از روش قبل خواهد بود. با توجه به اینکه معمولاً کاربر با اعداد بزرگ سر و کار ندارد و عملاً این روش بر روی  نیازهای کاربر تأثیر منفی نخواهد گذاشت. به عنوان یک نکته، تشخیص احتمال integer overflow ساده و قابل جلوگیری خواهد بود اگر هوشیار باشید و این احتمال را نادیده نگیرید. پس اگر محدودیت‌ها بر روی کاربر اعمال‌شده و فقط اشتباه برنامه‌نویس ممکن است علت integer overflow باشد، برای کمک به برنامه‌نویس (خود شما) بررسی امکان وقوع integer overflow را در ماکروی assert قرار دهید، تا درصورتیکه برنامه‌نویس (خود شما) سهواً اشتباه کرد، در مرحله تست و اشکال‌زدایی به او کمک شود تا ایراد را راحت‌تر پیدا کند، برای انتشار نهایی برنامه با تعریف ماکروی NDEBUG همهٔ شرطهای درون ماکروی assert نادیده گرفته خواهند شد.

نادیده‌گیری احتمال وقوع integer overflow می‌تواند صدمات امنیتی جدی را بر روی برنامه‌های مهم و بحرانی وارد کند، همانطور که شما دسترسی به اعضای آرایه را کنترل می‌کنید تا از محدودهٔ آرایه‌ها فراتر نروید، محدوده انواع عددی را نیز نباید فراموش کنید تا از صحت درستی محاسبات انجام‌شده اطمینان حاصل کنید.

ویرایش: بهرحال قبل از هر چیز بهتر است ابتدا محاسبات را طوری تغییر دهید که integer overflow رخ ندهد، اگر ممکن نبود، آنوقت از روشهای قبل استفاده کنید. ویرایش: جناب آقای توکلی، مثال بسیار خوبی را برای این حالت در قسمت دیدگاهها نوشته‌اند.

system call در گنو/لینوکس چیست؟

در سیستم‌عامل گنو/لینوکس، لینوکس در «فضای هسته» و کاربران در «فضای کاربر» قرار دارند. «فضای کاربر» همان ابزارها و بستر سیستم‌عامل گنو است که شما به عنوان کاربر با آن کار می‌کنید یا به عنوان برنامه‌نویس برای آن برنامه می‌نویسید. پس لینوکس به عنوان هستهٔ سیستم‌عامل گنو، رابطی به نام system call را برای پشتیبانی از «فضای کاربر» در «فضای هسته» پیاده‌سازی کرده است. در نتیجه نقش system call رابطی بین «فضای هسته» و «فضای کاربر» است تا برنامه‌نویس در گنو/لینوکس بتواند با استفاده از system call با هسته (لینوکس) رابطه برقرار کند.

اما برویم بر سر بحث اصلی! system call چطور کار می‌کند؟

لیست تمام توابع سطح پائینی که توسط هسته ارائه‌شده در جدولی به نام «جدول system call» نگهداری می‌شود، بر اساس ترتیب قرارگیری نام توابع در این جدول، یک شماره به هر یک از آنها اختصاص می‌یابد که به آن، شمارهٔ system call گفته می‌شود. برنامه‌نویس می‌تواند هر یک از این توابع را بر اساس شمارهٔ آنها توسط تابع syscall (به عنوان رابط) از درون هسته صدا بزند. البته نیازی به حفظ کردن این شماره‌ها نیست، زیرا به ازای هر یک از این توابع، یک ماکروی هم‌نام برای نگهداری شمارهٔ تخصیص‌یافته وجود دارد و برنامه‌نویس برای خوانایی بیشتر از این ماکروها استفاده می‌کند. به عنوان مثال برای دریافت ID پردازش فعلی از طریق system call بصورت زیر می‌نویسیم:

syscall(__NR_getpid);

مقدار ماکروی ‪__NR_getpid‬ برابر 20 خواهد بود که این عدد، همان شماره تخصیص‌یافته یا شمارهٔ system call برای تابع مورد نظر در هسته است. به دستور قبل، یک system call گفته می‌شود. چون این system call کاری مشابه تابع getpid در POSIX را انجام می‌دهد، پس می‌توان گفت که تابع getpid بعنوان لایه‌ای بر روی این system call عمل می‌کند.

این مطلب، ساده‌سازی خلاصه‌ای برگرفته‌شده از محتویات این لینک است، اطلاعات تکمیلی را می‌توانید در آن مطالعه کنید که چطور می‌توان یک system call را درون هسته (لینوکس) ایجاد نمود.

NULL برای تعیین طول و تعداد

طول آرایه‌ها در زبان C یا ‪C++‬ ذخیره نمی‌شود، پس این وظیفهٔ برنامه‌نویس است که طول آن را در یک متغیر جداگانه ذخیره نماید. اما یک روش جالب! آخرین عضو آرایه را NULL (برابر صفر) قرار دهید، از این پس بسیار ساده می‌توانید طول آرایه را تشخیص داده و به تعداد اعضای آن، با آرایه کار کنید:

MyStruct a, b, c;
...
MyStruct* var_array[] = {&a, &b, &c, NULL};

for(int i = 0; var_array[i] != NULL; ++i)
{
    std::cout << var_array[i]->value << std::endl;
}

.
MyStruct یک نوع ساختاری سازگار با زبان C است و دارای متغیری به نام value است. به کمک حلقهٔ for تمام اعضای آرایهٔ var_array مورد پویش قرار می‌گیرد تا زمانیکه به عضو تهی یا NULL برسد.

همچنین تعداد آرگومان‌های توابع در زبان C یا ‪C++‬ می‌تواند نامشخص باشد که به آنها توابع variadic گفته می‌شود، چنین توابعی می‌توانند به تعداد دلخواه، پارامتر دریافت کنند. برای تشخیص تعداد پارامترهای ارسال‌شده می‌توان از یک پارامتر کمکی استفاده کرد یا به عنوان روشی دیگر، می‌توان آخرین پارامتر ارسال شده را NULL در نظر گرفت:

void my_function(MyStruct* x, ...);

تابع my_function یک تابع variadic است و تعداد پارامترهای متفاوتی را می‌توان به جای علامت سه‌نقطه به این تابع فرستاد. به فرض اگر همهٔ پارامترهایی که این تابع می‌گیرد از نوع MyStruct باشد، برای دریافت پارامترهای ارسال‌شده به این توابع می‌توان از فایل سرآیند stdarg.h (یا cstdarg) کمک گرفت یا بطور مستقیم با آدرس آنها کار کرد:

int main()
{
    MyStruct a, b, c;
    ...
    my_function(&a, &b, &c, NULL);
}

void my_function(MyStruct* x, ...)
{
    for(MyStruct** p = &x; *p != NULL; ++p)
    {
        std::cout << (*p)->value << std::endl;
    }
}

.
استفاده از NULL برای تعیین تعداد پارامترهای ارسال شده به توابع variadic یا برای تعیین تعداد اعضای یک آرایه، می‌تواند روش مناسبی بوده و باعث افزایش خوانایی کدها گردد.

ویرایش: به جای NULL برای مقادیر انواع عددی می‌توانید از منفی یک استفاده کنید، بطور کلی با توجه به نوع داده می‌توانید یک مقدار منحصربه‌فرد را به جای NULL مورد استفاده قرار دهید.

Anjuta 2.30 همان IDE که منتظرش بودید.

چند روزی است از Anjuta 2.30 استفاده می‌کنم، به نظر من یکی از بهترین محیط‌های توسعهٔ یکپارچه (IDE) است، در نسخهٔ جدید Anjuta قابلیت auto-completion پیشرفت قابل توجهی داشته و بصورت هوشمند کار می‌کند (Code Intellisense). پروژه‌های ساخته‌شده رابطهٔ تنگاتنگی با GNU Autotools (ابزارهای autoconf و automake و libtool و …) دارند. قابلیت اشکال‌زدایی برنامه، ابزارهای کمکی برای تشخیص threadها، نمایش وضعیت call stack و متغیرهای محلی و امکان استفاده از watchها را نیز دارد. بطور کلی امکانات این IDE را می‌توان بصورت زیر خلاصه نوشت:

  • استفاده از GNU Autotools برای پروژه‌ها
  • محیط یکپارچه با طراح رابط کاربری Glade
  • محیط یکپارچه با Devhelp برای مستندات API
  • Code Intellisense (همان auto-completion بصورت هوشمند)
  • اشکال‌زدایی برنامه توسط ابزارهای کمکی
  • قابلیت استفاده از افزونه‌ها
  • سادگی و قابلیت انعطاف زیاد IDE
  • Syntax Highlighting بطور پیش‌فرض توسط GtkSourceView
  • تعیین خودکار تورفتگی‌ها توسط ابزار Auto-intent
  • wizard برای ایجاد یک کلاس جدید از GObject در زبان C
  • نمایش امضای تابع بصورت شناور در زمان نوشتن پارامترها
  • جستجو و جایگزینی پیشرفته در کد منبع

برای نصب آن در دبیان testing (و اوبونتو، پارسیکس، …) دستور زیر را وارد کنید:

# apt-get install anjuta

همچنین اگر بستهٔ anjuta-extra را نصب کنید، قابلیت «نمایش نمودار ارث‌بری کلاس» اضافه شده است.

[+] وب‌سایت رسمی پروژه
[+] لیست قابلیت‌ها به همراه تصویر

کامپایل سریعتر با دستور make

معمولاً کد منبع برنامه‌های گنو/لینوکس دارای فایلی به نام Makefile است تا کاربر توسط دستور make بتواند آن برنامه را کامپایل و بر روی سیستم خودش نصب کند. بطور مثال، کد منبع یک برنامه را دانلود و غیر فشرده کرده‌اید، برای پیکربندی، کامپایل و نصب آن به ترتیب دستورات زیر را وارد می‌کنید:

./configure
make
make install

اگر از CPUی چندهسته‌ای یا از سری CPUهای core i7 و … اینتل استفاده می‌کنید، دستور دوم (دستور make) فقط توسط یکی از هسته‌ها پردازش می‌شود و در طی این مدت، سایر هسته‌ها بدون استفاده خواهند ماند و چون دستور make زمان‌برترین (و طولانی‌ترین) مرحله است، در نتیجه با توجه به حجم کد منبع، زمان زیادی را منتظر خواهید ماند تا مرحلهٔ کامپایل (دستور make) بطور کامل انجام شود. اگر برای اجرای دستور make بتوان از قدرت پردازش سایر هسته‌ها نیز استفاده کرد، مسلماً سرعت اجرای دستور make به چندین برابر افزایش خواهد یافت. به همین خاطر است که توسط سوئیچ j می‌توان تعداد هسته‌ها (یا threadهای ناهمزمان) را مشخص کرد:

./configure
make -j 4
make install

در این حالت، مشخص شده که بطور همزمان ۴ نخ (thread) مورد استفاده قرار گیرد، بنابراین اگر از یک CPUی ۴ هسته‌ای استفاده می‌کنید، هر thread توسط یک هسته مورد پردازش قرار خواهد گرفت و بدین طریق، سرعت دستور make نسبت به حالت قبل (بدون استفاده از سوئیچ j) تقریباً چهاربرابر افزایش خواهد یافت. این یک مزیت در استفاده از دستور make است که قابلیت استفادهٔ حداکثر از توانایی سخت‌افزار را دارد.

دنبال‌کردن

هر نوشتهٔ تازه‌ای را در نامه‌دان خود دریافت نمایید.