در اصطلاحات کامپیوتری، کامپایلر (Compiler) برنامهای است که کد نوشته شده به یک زبان برنامهنویسی (زبان مبدا) را به یک زبان دیگر (زبان مقصد) ترجمه میکند. معمولا از اصطلاح کامپایلر برای اشاره به برنامههایی استفاده میشود که کد مبدا را از یک زبان برنامهنویسی سطح بالا به زبانی سطح پایینتر ترجمه میکند تا برنامهای قابل اجرا ایجاد کند؛ انواع بسیار مختلفی از کامپایلرها وجود دارند که کد خروجی را به شکلهای مختلفی ایجاد میکنند؛ در این مقاله از همیار آیتی به زبان ساده با کامپایلرها آشنا شده و انواع آنها را بررسی میکنیم.
تعریف کامپایلر به زبان ساده
کامپایلر، برنامهای است که کدهای نوشته شده به زبانی که برای انسان قابل درک است را به کدهایی تبدیل میکند که برای ماشین قابل درک باشد. این برنامهها با نزدیک کردن کدهای برنامهنویسی به کدهای سطح ماشین و سختافزاری عمل میکنند. یک منبع کد سطح بالا که توسط یک توسعهدهنده به یکی از زبانهای سطح بالا نوشته شده باشد توسط کامپایلر به کد سطح پایینتر ترجمه میشود تا نتایج برای پردازنده کامپیوتری قابل تفسیر باشند.
اغلب اوقات، خروجی کامپایلر را تحت عنوان کد ابژه (Object) یا گاهی ماژول ابژه میشناسند. این کد چیزی جز یک کد ماشینی که پردازنده میتواند هر دستور آن را جدا جدا اجرا کند نیست. کامپایلرها به این دلیل مورد نیاز هستند که پردازندههای سنتی به روش متفاوتی کد ابژه را اجرا میکنند. پردازنده از درگاههای منطقی برای انتقال سیگنالها روی برد استفاده میکند و افت و اوجهای دوگانه سیگنالها را برای کار واحد منطقی حسابی کامپیوتر تغییر میدهد.
اما این همان کدی نیست که یک برنامهنویس ایجاد میکند، بر خلاف این زبان ماشینی دستوری و دوگانه، کد سطح بالای اولیه شامل متغیرها، فرمانها، تابعها، فراخوانها، متدها و انواع مختلف کارکردها است که در آمیزهای از نحو حسابی و زبانی مرتب شده است؛ اما تمام این موارد لازم است به فرمی دربیاید که یک کامپیوتر قادر به درک آن و اجرای برنامه باشد.
نحوه کار کامپایلرها چگونه است؟
برنامههای کامپایلر برای ترجمه کردن کد قابل درک انسان به کد قابل درک برای ماشین کاربرد دارند. این فرایند تحت عنوان کامپایل کردن شناخته میشود که در آن قواعد نحوی یک زبان برنامهنویسی به یک زبان برنامهنویسی دیگر ترجمه میشود. با این حال نباید از یاد برد که کامپایلر تنها یک برنامه است و توانایی برطرف کردن ایراد کدهای نوشته شده توسط انسان را ندارد. به این ترتیب اگر کد اولیه برنامه اشکال و ایرادی داشته باشد، تا زمانی که این ایرادها بر طرف نشده باشند، کامپایلر نمیتواند آن را کامپایل کند.
پیچیدگی یک برنامه کامپایلر تا حد زیادی به نحو زبان برنامه نویسی و میزان انتزاعی که این زبان عرضه میکند بستگی دارد. در نتیجه یک کامپایلر زبان سی (C) به مراتب سادهتر از یک کامپایلر زبان سیپلاسپلاس (++C) یا سیشارپ (#C) عمل میکند. به هر ترتیب در هنگام وارد کردن کد به کامپایلر، اغلب مراحل زیر دنبال میشود.
اسکن کردن
در این مرحله کد مبدا حرف به حرف و به دنبال هم خوانده شده و این که هر حرف و کاراکتری در کدام خط قرار گرفته است مورد بررسی قرار میگیرد؛ در این مرحله در صورتی که کد از نظر حروف یا نحوی دچار اشکالی باشد این اشکال شناسایی میشود.
تحلیل حروف
در هنگام کامپایل کردن، برنامه کامپایلر اول جریان کاراکترها و حروفی که در یک فایل کد منبع وجود دارد را میخواند و جریانی از توکنهای حرفی ایجاد میکند. برای مثال، کد سیپلاسپلاس زیر را در نظر بگیرید:
int C = (A*B)+10;
این کد میتواند به این شکل توسط کامپایلر تحلیل شود: (نوع اینتجر) (متغیر C) (تخصیص) (پرانتز چپ) (متغیر A) (ضرب) (متغیر B) (پرانتز راست) (جمع) (عدد ۱۰) این توکنها توسط برنامه بررسی میشود و بر اساس همخوانی و سازگاری با زبان مقصد تحلیل میشوند.
تحلیل نحوی
خروجی تحلیل حروف وارد تحلیلگر نحوی برنامه کامپایلر میشود که از قواعد گرامر برای تصمیمگیری در مورد اعتبار ورودیها استفاده میکند. تحلیل نحوی با این پیشپردازش که آیا توکنهای ایجاد شده در تحلیل حرفی با ترتیب مناسب استفاده منتقل شده اند یا نه شروع میشود؛ ترتیب درست یک مجموعه کلمات کلیدی که میتواند منجر به نتیجهای دلخواه شود چیزی جز نحو نیست. کامپایلر لازم است که کد مبدا را بررسی کند تا اطمینان حاصل شود که دقت نحوی ایجاد شده است.
تحلیل معناشناختی
در مرحله آخر که تحلیل معناشناختی باشد، چندین فرایند اتفاق میافتد. اول از همه ساختار توکنهای از نو بررسی میشود، و ترتیب و محل قرارگیری آنها در دستور زبان یا گرامر یک زبان برنامهنویسی مشخص بررسی میشود. سپس معنای ساختار توکن توسط پارسر (Parser) و تحلیلگر برنامه تحلیل میشود تا در نهایت کدی اجرایی به دست بیاید که به آن کد ابژه گفته میشود.
کد ابژه شامل دستوراتی میشود که فعالیت پردازنده را در قبال یک توکن مشخص در برنامه مشخص میکند. در نهایت، تمام کد مبدا در پارسر بررسی و تحلیل میشود تا امکان بهینهسازیهای احتمالی بررسی شود. در صورتی که امکان بهینهسازی وجود داشته باشد، توکنهای اصلاح شده در کد ابژه قرار میگیرد تا کد ابژه نهایی به دست بیاید، که در نهایت در یک فایل ذخیره میشود.
آشنایی با انواع کامپایلرها
همان طور که پیشتر اشاره شد، با توجه به کد مبدا و مقصد مورد استفاده، انواع مختلفی از کامپایلرها وجود دارد. یکی از راههای طبقهبندی کامپایلرها بر اساس پلتفرمی است که آنها کد را برای آن ایجاد میکنند. معمولا به این مفهوم «پلتفرم هدف» میگویند. یک کامپایلر بومی یا میزبان شامل برنامهای میشود که خروجی آن با هدف اجرای مستقیم بر روی همان کامپیوتر و سیستم عاملی که کامپایلر روی آن کار میکند تهیه میشود.
در مقابل، کراس کامپایلرها برای این که کدهای مورد نیاز پلتفرمهای متفاوت را اجرایی کنند طراحی میشوند. علاوه بر این، بسیاری از کامپایلرها خروجی خود را روی یک ماشین مجازی (VM) اجرا میکنند. این دسته از کامپایلرها معمولا توان ایجاد خروجی برای پلتفرمهای دیگر را نیز دارا هستند.
هر چه سطح زبان هدف یک برنامه کامپایلر پایینتر باشد، خود کامپایلر از زبان برنامه نویسی سطح بالاتری استفاده میکند. برای مثال زبان سی، که از سوی بسیاری به عنوان یک زبان اسمبلی قابل حمل شناخته میشود، اغلب زبان هدف چنین کامپایلرهایی تعیین میشود. معمولا کد سی که از این روش ایجاد میشود برای خوانایی و نگهداری توسط انسانها ایجاد نمیشود، برای همین بسیاری از جزئیاتی که توسط برنامه نویس انسانی مورد توجه قرار میگیرند در این موارد کنار گذاشته میشود.
کامپایلرهای تک جهتی
در یک کامپایلر تک جهتی، کد مبدا وارد کامپایلر شده و فرایندهای تحلیلی که در بالا مورد اشاره قرار گرفت روی آن انجام شده و به کدی ماشینی ترجمه میشود. در این فرایند زبان مقصد معمولا یک زبان سطح پایین مانند زبان پاسکال است.
کامپایلرهای دو جهتی
کامپایلرهای دو جهتی به سادگی به دو بخش به نسبت مجزا از یکدیگر تقسیم میشوند: فرانت اند، و بک اند. مرحله فرانت اند شامل تفسیر کد اولیه به تحلیل اولیه میشود و بک اند شامل تفسیر این تحلیل به زبان ماشین هدف میشود. این نوع از کامپایلرها فرایند بازهدف گیری را سادهتر میکنند. همچنین امکان استفاده از چندین فرانت اند در این برنامهها وجود دارد.
کامپایلرهای چند جهتی
کامپایلرهای چند جهتی کد مبدا یا ساختار نحوی آن را چندین بار پردازش میکنند. در این کامپایلرها برنامههای بزرگ به چندین برنامه کوچکتر تقسیم شده و مورد پردازش قرار میگیرند. همچنین از آنها برای ایجاد چندین کد میانی استفاده میشود. ساختار کلی کامپایلرهای چند جهتی شبیه کامپایلرهای دو جهتی است، با این تفاوت که یک بخش میدل اند نیز در آنها قرار دارد که بین فرانت اند و بک اند قرار میگیرد.
این نوع کامپایلرها چندین کد میانی ایجاد میکنند که خروجی فاز قبل را به عنوان ورودی تعریف میکنند. به این ترتیب آنها همچنین نیاز به حافظه کمتری هم دارند. به این دسته از کامپایلر پهن هم گفته میشود. به جز این موارد، انواع کامپایلرها از لحاظ زبان مقصد و مبدا، خروجی، و یا مختص سختافزار بودن آنها از یکدیگر تفکیک میشوند.
به صورت کلی هر جا که یک کد برنامه نویسی سطح بالا که برای انسان قابل درکتر است قرار باشد به کدی ترجمه شود که برای ماشین یا کامپیوتر قابل درک باشد، باید از یک کامپایلر استفاده شود. با توجه به میزان پردازش و نحوه انجام تحلیلهای گوناگونی که در این فرایند انجام میشود، انواع مختلفی از کامپایلرها وجود دارند. هر یک از این برنامهها برای کامپایل و ترجمه کردن نوع خاصی از برنامهها یا کدها کارایی دارند.
انواع مختلف برنامههای کامپایلر بر اساس میزان پردازشی که بر روی کد مبدا انجام میدهند به برنامه کامپایلر تک جهتی، دو جهتی، و چند جهتی تقسیم میشوند. همان طور که از نام آنها میتوان حدس زد، هر کدام از این نوعها دارای یک مرحله پردازش بیشتر از قبلی است. یعنی در حالی که کامپایلر تک جهتی صرفا میتواند کد را از نظر همخوانی و سازگاری با زبان سطح پایین مورد نظر بررسی کند، در کامپایلرهای دو جهتی این بررسی چند لایه شده و از جهات گوناگون انجام میشود، و در کامپایلرهای چند جهتی به اوج خود در امکان تولید خروجیهای مختلف میرسد.