حذف کد مرده

در نظریه کامپایلر، شاخه‌ای از علوم کامپیوتر نظری، حذف کد مرده (به انگلیسی: Dead code elimination) نوعی از بهینه‌سازی کامپایلر (به انگلیسی: Compiler optimization) است، که برای حذف بخشی از کد، که در نتیجهٔ برنامه تأثیری ندارد، استفاده می‌شود. کد مرده شامل بخشی از کد است که هیچ‌گاه اجرا نمی‌شود و یا در صورت اجرا، خروجی آن مورد استفاده قرار نگرفته و در واقع فقط بر روی متغیرهای مرده (متغیرهایی که در بقیه بخش‌های برنامه خوانده نمی‌شوند و از آن‌ها استفاده‌ای نمی‌شود) تأثیر می‌گذارد. به‌طور معمول، کامپایلرها در زمان کامپایل برنامه، به صورت بازگشتی تعیین می‌کنند که کدام متغیرها  و بخش‌های برنامه استفاده شده‌اند. متغیرها و بخش‌های استفاده شده علامت گذاری می‌شوند و در نهایت بخش‌های علامت نخورده، از برنامه حذف می‌شوند. اکثر کامپایلرهای پیشرفته، گزینه‌های متعددی را در سطوح مختلف برای حذف کد مرده در اختیار قرار می‌دهند. در سطوح پایین، کامپایلر فقط بخش‌هایی از کد که اجرای آن‌های غیرممکن است را حذف می‌کند. در سطوح بالاتر کامپایلر فضایی را برای ذخیره‌سازی متغیرهای بی‌استفاده در نظر نمی‌گیرد. در سطوح بالاتر از آن نیز، کامپایلر دستورها و توابع بی‌استفاده را شناسایی و حذف می‌کند.

مزایا

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

خطرات

از طرفی باید در عملیات حذف کد مرده بسیار دقت نمود، چرا که ممکن است نتیجهٔ محاسبات بخشی از کد، در جای دیگری استفاده نشود، ولی آن محاسبات باعث ایجاد تغییر در یک متغیر سراسری و یا تولید استثنا (به انگلیسی: Exception) شود، که در این صورت حذف آن بخش از کد، باعث ایجاد خطا در عملکرد برنامه می‌شود. به‌طور معمول، کامپایلرها زمانی که در مورد مرده بودن بخشی از کد ابهام هست، به صورت محافظ کارانه عمل می‌کنند.

مثال‌ها

این برنامه که با زبان سی (C) نوشته شده‌است را در نظر می‌گیریم.
 1  int foo(void) 2  { 3    int a = 24; 4    int b = 25; /* Assignment to dead variable */ 5    int c; 6    c = a * 4; 7    return c; 8    b = 24; /* Unreachable code */ 9    return 0;10  }
تحلیلی ساده از کاربرد مقادیر، مشخص می‌کند که متغیر b بعد از این‌که برای اولین بار در خط چهارم مقداردهی می‌شود، در تابع foo استفاده نمی‌شود. از طرفی، متغیر b یک متغیر محلی در تابع foo است و به این دلیل نمی‌تواند خارج از foo استفاده شود. بنابراین متغیر b مرده است و کامپایلر در زمان بهینه‌سازی می‌تواند آن‌را حذف نماید.
همچنین از آن‌جا که تابع همواره در خط هفتم، با دستور بازگشت به کار خود پایان می‌دهد، هیچ مسیر اجرایی به خط هشتم که متغیر b برای دومین بار در آن مقداردهی می‌شود نمی‌رسد. بنابراین، دسترسی به این مقداردهی امکان‌پذیر نمی‌باشد و این بخش از کد، قابل حذف شدن است. باید دقت شود که اگر برنامه ساختار پیچیده تری داشت، به عنوان مثال بعد از دستور بازگشت از تابع در خط هفتم، برچسبی وجود داشت و از جای دیگری از برنامه دستور goto به آن برچسب بود، آن موقع ممکن بود مسیر اجرایی به دومین مقداردهی متغیر b وجود داشته باشد و دیگر b غیرقابل دسترس نبود.
علاوه بر این، با این‌که محاسباتی در این تابع انجام می‌شود، مقدار آن محاسبات در جایی که خارج از حوزهٔ این تابع قابل دسترسی باشد ذخیره نمی‌شود. بنابراین با توجه به اینکه تابع همواره مقدار ۹۶ را بازمی‌گرداند، ممکن است در زمان بهینه‌سازی به مقداری که بازمی‌گرداند ساده شود.

حال برنامهٔ دیگری که با زبان سی نوشته شده‌است را در نظر می‌گیریم.
 1  int main(void) { 2    int a = 5; 3    int b = 6; 4    int c; 5    c = a * (b / 2); 6    if (0) {   /* DEBUG */ 7      printf("%d\n", c); 8    } 9    return c;10  }
در این مثال نیز از آنجا که مقدار صفر، همواره برابر با false در نظر گرفته می‌شود، دستور if هیچ‌گاه اجرا نخواهد شد و بنابراین کل بخش if در هنگام بهینه‌سازی از برنامه حذف می‌شود.

 حذف پویای کد مرده

گاهی اوقات غیرقابل دسترس و مرده بودن بخشی از کد، در شرایط خاصی اتفاق می‌افتد و در هنگام کامپایل مشخص نمی‌شود. به عنوان مثال این شرایط را ورودی برنامه‌ها می‌توانند ایجاد کنند و باعث شوند بخشی از کد هیچ‌گاه در زمان اجرای برنامه، اجرا نشوند. بخشی از کد به صورت پویا مرده (به انگلیسی: Dynamically dead) است اگر بسته به شرایط برنامه در زمان اجرا، مقادیری که آن بخش از کد تولید می‌کند، در جای دیگری از برنامه استفاده نشود. به بیان دیگر، یک دستور به صورت پویا مرده، نمونه‌ای از یک دستور ایستا (به انگلیسی: Static instruction) است، که یک مقدار ثبات (به انگلیسی: Register) مرده را ایجاد می‌کند. محققان دریافته‌اند که کسر عظیمی از دستورات اجرا شدهٔ برنامه‌ها به صورت پویا مرده‌اند.