سلام به مهندس های خوشکل
چند مین پیش یکسری اطلاعات از یک وبلاگی دزدیم اما بعدش وجدان درد گرفتم
اموزش رشنال روز وUML
http://softwareengineer.blogsky.com/category/cat-9
از منبع اصلی به فیض برسین بهتره
در تاریخچه کامپایلر سه دوره میتوان در نظر گرفت:
در این دوره ,زبانها به تدریج به وجود آمدند و ماشینها چندان متعارف نبودند . مسئله این بود که چگونه باید کدی را برای یک ماشین تولید کرد . با توجه به اینکه برنامه نویسی به زبان اسمبلی رواج داشت , این مسئله وخیمتر شد. استفاده از کامپایلر , برنامه نویسی خودکار نامیده شد . طرفداران زبانهای سطح بالا میترسیدند که کد تولید شده نسبت به زبان اسمبلی کارایی چندان نداشته باشد. اولین کامپایلر فرترن(شریدان 1959) به خوبی بهینه سازی شد
در دهههای 1960و1970 زبانهای برنامهسازی جدید به وجود آمدند و طراحان زبان معتقد بودند که طراحی سریع کامپایلر برای زبان جدید , مهمتر از وجود کامپایلری با کد کارآمد است .بدین ترتیب , در ساخت کامپایلر به پردازشگر جلویی تاکید شده است . در همین زمان , مطالعه زبانهای رسمی , تکنیکهای قدرتمندی را برای ساخت پردازشگر جلوی , بخصوص تولید تجزیه کننده به وجود آورد
از 1975 تاکنون , تعداد زبانهای جدید و انواع ماشین مختلف کاهش یافت در نتیجه نیاز به کامپایلرهای سریع و ساده یا سریع و ناقص برای زبانها یا ماشینهای جدید , کاهش یافت . بزرگترین آشفتگی در طراحی زبان و ماشین خاتمه یافت و افراد خواستار کامپایلرهای قابل اعتماد , کارآمد و با واسط کاربر مناسب شدند . بدین ترتیب , توجه کیفی به کد بیشتر شد زیرا با تغیر اندکی که در ساختار ماشینها ایجاد میشود , طول عمر کدها افزایش مییابد.در همین دوره , مدلهایی در برنامه نویسی به وجود آمدند که برنامه نویسی تابعی , منطقی و توزیعی نمونههای از این مدلها هستند, خواستههای زمان اجرای این زبانها نسبت به زبانهای دستور, افزایش یافت .
به طور کلی , کامپایلر برنامهای است که متن برنامهای را که به یک زبان برنامهسازی نوشته شده است ,به عنوان ورودی میپذیرد , و خروجی آن , متن برنامهای به یک زبان دیگر است , به طوری که معنای آن متن تغییر نمیکند. این فرآیند , در زبان طبیعی , ترجمه نام دارد. مترجمها جملات یک زبان طبیعی را به زبان طبیعی دیگر ترجمه میکنند. تقریبا تمام کامپایلرها ,برنامهای به یک زبان منبع را گرفته به برنامهای به زبان مقصد تبدیل میکنند . به عنوان مثال , زبان منبع میتواند c و زبان مقصد میتواند زبان ماشین برای کامپیوتر پنتیوم باشد. زبانی که خود کامپایلر با آن نوشته میشود, زبان پیاده ساز نام دارد.
علت انجام ترجمه این است که , سخت افزار عملیاتی را انجام میدهد که توسط معنای برنامه توصیف میشود . به طور کلی , سخت افزار تنها منبع واقعی انجام محاسبات است. اجرای برنامه ترجمه شده, شامل خواندن دادههای آن بار فرمت خاص , و چاپ نتایج با فرمت دیگری است . دادههای ورودی میتواند از منابع متعددی خوانده شود , مثل فایلها , صفحه کلید و پکیجهای شبکهبه همین ترتیب , خروجی نیز میتواند در فایلها , صفحه نمایش رایانه , و چاپگر قرار گیرد .
برای ترجمه برنامه,از کامپایلر استفاده میکنیم . کامپایلر برنامهای است که ورودی آن، , فایلی با فرمت متن برنامه و خرجی آن، فایلی با فرمت کد اجرایی است .
برای تهیه یک کامپایلر , کامپایلر دیگری رااجرا میکنیم که ورودی آن ,متن منبع این کامپایلر و خروجی آن , کد اجرایی برای این کامپایلر است . این فرآیند کامپایل است . اگر زبان منبع , همان زبان پیاده ساز باشد, و متن منبعی که کامپایل میشود, نسخه جدید خود کامپایلر باشد, این فرآیند خودرانی نام دارد.
کامپایل کردن یک برنامه, با تبدیل فرمت یک فایل به فرمت دیگر , مثل EBCDIC به اسکی تفاوت عمدهای ندارد . در کامپایل کردن برنامه, معنای برنامه باید حفظ شود . به دو دلیل زیر کامپایلر میتواند کار کند:
· ورودی, به یک زبان برنامه سازی است و در نتیجه دارای ساختاری است که در مراجع آن زبان مشخص شده است .
· معنای ورودی بر اساس این ساختار توصیف میشود ,و به آن ساختار مربوط است.
این عوامل موجب میشوند تا کامپایلر برنامه را "درک کند " و معنای آن را در یک نمایش معنایی جمع آوری کند .هریک از دو عامل فوق , در زبان مقصد نیز وجود دارد . بدین ترتیب , کامپایلر میتواند معنای جمع آوری شده را بر حسب ساختار زبان مقصد ارائه کند .
بخشی از کامپایلر که متن زبان منبع را تحلیل میکند , پردازشگر جلویی و بخشی از کامپایلر که زبان مقصد را ترکیب میکند, پردازشگر نهایی نام دارد اگر کامپایلر به خوبی طراحی شده باشد, پردازشگر نهایی, کاملا از زبان مبدا و پردازشگر جلویی از زبان منبع بی خبر است . هر یک از این دو بخش از نمایش معنایی برنامه خبر دارند .این تفکیک دقیق , دلایل فنی دارد و منجر به کامپایلرهای ساخت یافته میشود.
حالت دیگری از عملیات برای کامپایلر پیشنهاد میشود: اگر تمام دادههای ورودی مورد نیاز , فراهم باشند, کامپایلر میتواند مفهوم مشخص شده توسط معنای برنامه را بدون تبدیل به شکل دیگر , اجرا نماید . در این صورت به جای مولد کد پردازشگر نهایی, مفسر پردازشگر نهایی قرار میگیرد و این برنامه, مفسر نامیده میشود . چند دلیل برای این کار وجود دارد . یکی از دلایل مهم این است که مفسر معمولا در ماشین خاصی اجرا میگردد. به عبارت دیگر , استفاده از مفسر منجر به افزایش قابلیت حمل میگردد. دلیل دیگر این است که نوشتن مفسر, آسانتر از پردازشگر نهاییت کامپایلر است .دلیل سوم برای استفاده از مفسر به جای کامپایلر این است که , اجرای مستقیم برنامه , عیب یابی و گزارش خطا را آسانتر میکند . دلیل چهارم این است که امنیت از طریق مفسرها افزایش مییابد . این کار , در جاوا اهمیت فراوانی دارد.
هر کامپایلر از قسمت های اصلی زیر تشکیل شده است:
و نیز دو بخش کمکی:
راه های مختلفی جهت دسته بندی کامپایلر ها وجود دارد مثلا می توان آنها را با توجه به ورودی، خروجی، ساختار داخلی و یا رفتار زمان اجرای آن تقسیم بندی کرد.
اکثر کامپایلرها به دو دسته Native و Cross تقسیم می شوند. کامپایلرهایی که به منظور اجرای برنامه ها کدهای باینری را تولید می کنند، کامپایلر هایی با کد محلی یا Native گوییم چرا که تنها در کامپیوترهای یک نوع با سیستم عامل های یکسان قابل به کارگیری است. از طرف دیگر ممکن است کامپایلرها کدهای باینری را تولید کنند که در سیستم های مختلف قابل اجرا باشد. به این دسته از کامپایلر ها که وابستگی به سخت افزار ندارند، کامپایلر های عبوری یا Cross گوییم. برای این نوع کاپایلر ها تنها کافی است برای بار اول سخت افزار را به آن معرفی نمود. بنابراین می توان نتیجه گرفت که کامپایلرهای عبوری مفیدتر هستند. این تقسیم بندی برای مفسرها به کار نمی رود جونکه آنها از نمایش دودویی برای اجرای کد خود استفاده نمی کنند. ماشین های مجازی در هیچ یک از این دسته بندی ها نمی گنجد. هر گاه در ماشین های مجازی یکسان قابل اجرا باشد می توان آنرا Native و هرگاه کامپایلر قادر به تولید خروجی برای پلت فورم های مختلف باشد آنرا Cross گوییم.
فاز بندی کامپایلر ها که در پشت زمینه به محدودیت های منابع سخت افزاری وابسته است. در نتیجه کامپایلر ها به مجموعه برنامه های کوچکتر تقسیم می شوند هر یک بخشی از عمل ترجمه یا آنالیز را برعهده می گیرند. کامپایل تک فازی به نظر مفید می آید، چراکه سریعتر است. زبان پاسکال از این امکان استفاده می کند. اما مشکل اینجا است که اگر اعلان جلوتر از دستور به کارگیری باشد، چه کار باید کرد؟ برای حل این مشکل میتوان در فاز اول اعلان ها را مشخص کرد و در فاز بعد عمل ترجمه را انجام داد. عیب دیگر کامپایلر تک فازی دشواری بهینه سازی کدهای زبان سطح بالا می باشد. همگردان یکگذره (One-Pass Compiler) کامپایلری است که برای تولید کد ماشین، تنها یک مرتبه متن برنامه را میخواند. دستور برخی زبانها به گونهای است که تولید همگردان یکگذره برای آنها غیر ممکن است. مجموعه همگردان های گنو یا Gnu complier colection یا به صورت مخفف GCC مجموعه ای از همگردان های آزاد برای زبان های برنامه نویسی است. تقسم بندی کامپایلر ها به برنامه های کوچکتر تکنیکی است که همچنان مورد بحث محققان است. در این نوع دسته بندی کامپایلر ها، انواع دیگری نیز وجود دارد:
بسیاری از افراد زبانهای سطح بالا را به دو دسته تفسیری و کامپایلی تقسیم می کنند. کامپایلر ها و مفسر ها روی زبان ها عمل می کنند نه زبانها روی آنها! مثلا این تصور وجود دارد که الزاما BASIC تفسیر می شود و C کامپایل. اما ممکن است نمونه هایی از BASIC یا C ارائه شود که به ترتیب کامپایلری و تفسیری باشد. البته استثنا هایی نیز وجود دارد، مثلا برخی زبانها در خصوصیات خود این تقسیم بندی را مشخص کرده اند(C کامپایلری است یا SNOBOL4 و اکثر زبانهای اسکریپتی که کد منبع زمان اجرا دارند تفسیری می باشد).
تقسیم بندی پروسه های کامپایل به مجموعه ای از فاز ها مورد حمایت پروژه کامپایلری (( تولید کامپایلرهای باکیفیت ))(PQCC) از دانشگاه Carnegie Mellon قرار گرفت. در این پروژه اصطلاحات جلو بندی، میان بندی(امروزه به ندرت به کار میرود) و عقب بندی معرفی شد. اکثر کامپایلرهای امروزی بیش از دو فاز دارند. جلوبندی معمولا با پردازش املایی و معنایی شرح داده می شود. عقب بندی شامل تبدیل نوع و بهینه سازی های مختلف می باشد. سپس کد برای آن کامپیوتر خاص تولید می شود. استفاده از جلوبندی و عقب بندی این را ممکن می کند که جلوبندی های مختلفی برای زبانهای مختلف وجود داشته باشد و عقب بندی های مختلفی نیز برای CPU های مختلف.
جلوبندی به منظور تولید کد میانی یا IR از کد مبدا استفاده می شود. جلوبندی معمولا جدول نماد ها را مدیریت نموده و یک نگاشتگر ساختمان داده ای، هر نماد را از درون کد مبدا به اطلاعات مربوط به آن مثل نوع و دامنه تعریف آن نگاشت می شود. این امر در چند فاز انجام میگردد:
1. خط نوسازی. زبانهایی که اجازه تعیین فضای اختیاری برای شناسه ها را می دهند قبل از عمل تجزیه نیاز به فاز اضافی دارند که کد ورودی را به صورت متعارفی برای تجزیه گر آماده کند. Algol، Coral66، Atlas Autocode وImp نمونه هایی از این زبانه هستند که به خط نوسازی (Line Reconstruction) نیازمند است.
2. پیش پردازش. برخی زبانها همچون C احتیاج به فاز پیش پردازش برای جایگزینی شروط کامپایل و ماکرو ها دارند.در زبان C فاز پیش پردازش شامل مرحله تحلیل لغوی می شود.
3. تحلیل لغوی کد متنی مبدا را به اجزای کوچکی که نشانه(token) نامیده می شود می شکند. هر نشانه واحد ساده ای از زبان است مثل کلمات کلیدی و نام نمادها. نحو نشانه ها نوعا یک زبان باقاعده است، بنابراین یک ماشین حالت متناهی که برپایه یک عبارت باقاعده بنا می شود می تواند جهت شناخت آن استفاده شود.
4. تحلیل نحوی شامل تجزیه کردن نشانه های مرتب جهت شناخت ساختار نحوی زبان می باشد.
5. تحلیل معنایی فازی است که معنای برنامه را جهت رعایت قوانین زبان بررسی می کند. یک مثال برای این فاز کنترل نوع است.
گاهی مرحله عقب بندی با مرحله تولید کد اشتباه گرفته می شود. اما می توان گفت که عقب بندی به مراحل چند گانه زیر تقسیم می شود:
1. تحلیل کامپایلر: این پروسه برای بدست آوردن اطلاعات بیشتر از نمایش میانی فایل های ورودی می باشد. تحلیلگر نوعی تعاریف مختلفی دارد همچون تحلیلگر حلقوی، تحلیلگر وابسطه، تحلیلگر مستعار، تحلیلگر اشاره ای یا غیره می باشد. تحلیل دقیق زیر بنای هر کامپایلرهای بهینه است. گراف فراخوانی و نمودار جریان کنترل معمولا در فاز تجزیه تولید می گردد.
2. بهینه سازی: نمایش میانی زبان به معادل های پر سرعت تر با شکل های کوتاه تری تبدیل می گردد. از بهینه ساز های محبوبتر می توان به موارد زیر اشاره نمود: توسعه درون خطی، حذف کد های مرده، انتشار ثوابت، تبدیل حلقه ها، تخصیص های ثباتی و موازی سازی خودکار.
3. تولید کننده کد: زبان میانی تغییر کرده به زبان خروجی مثل زبان ماشین ترجمه می شود. این شامل تخصیص منابع و تصمیمات ذخیره سازی است، مثلا اینکه کدام متغیر به رجستر ها یا حافظه اختصاص یابد و گزینش و زمانبندی دستورات مناسب ماشین .
" البته در ابتدای امر که در مورد زبانهای تفسیری و
کامپایلری گفته بودند باید خاطر نشان کرد که زبانهای تفسیری خط به خط
خوانده شده و اجرا میگردد در حالیکه در کامپایلری ابتدا تمام برنامه ترجمه
شده و سپس اجرا میگردد پس در زمان اجرا سرعت اجرا شدن زبانهای کامپایلری
بیشتر است. اما کشف و تصحیح خطا در تفسیری بهتر و راحت تر است ."
gcc از ابتدا مخفف Gnu C Compiler بود ولی از زمانی که توانست زبانهای دیگری غیر از C از قبیل C++,Ada,Java,Objective C و Fortran را کامپایل کند بهGnu Compiler Colection تغییر نام داد. پدید آورنده اصلی GCC ریچارد استالمن است کسی که بنیانگذار پروژه Gnu محسوب می شود. نخستین نسخه GCC در سال 1987 انتشار یافت که یک پیشرفت مهم محسوب می شد زیرا محصول جدید اولین کامپایلر بهینه سازی شده قابل حمل ANSI C به عنوان یک نرم افزار آزاد محسوب می شد. در سال 1992 نسخه 2.0 کامپایلر GCC عرضه شد. نسخه جدید قابلیت کامپایل کدهای ++C را نیز داشت. در سال 1997 یک انشعاب آزمایشی در GCC به نام EGCC به منظور بهینه سازی کامپیایلر و پشتیبانی کامل تر از ++C ایجاد شد. در ادامه EGCC به عنوان نسل بعدی کامپایلر GCC پذیرفته شد و تکامل آن باعث انتشار نسخه سوم GCC در سال 2004 گردید. چهارمین نسخه از کامپایلر GCC در سال 2005 عرضه شد.
ساختار کامپایلر
اکنون شرح مختصری از هر پیمانه را ارائه خواهیم کرد:
پیمانه ورودی متن برنامه ، فایل متن برنامه را مییابد، آن را میخواند، و آن را به صورت جریانی از کاراکترها تحویل میدهد . ممکن است به فایلیهای دیگری که به یان فایل ضمیمه شدهاند . سربزند . این عمل ممکن است نیاز به همکاری سیسم عامل و تحلیل گر لغوی داشته باشد.
پیمانه تحلیل لغوی نشانههای موجودی در جریان ورودی را جدا میکند ورده و نمایش آنها را تعیین مینماید. این پیمانه را میتوان به صور دستی نوشت یا از توصیف نشانهها تولید نمود. علاوهبراین , ممکن است تفسیرهای محدودی را بر روی بعضی از نشانهها انجام دهد :به عنوان مثال، تعیین کند که آیا شناسهای یک ماکرو است یا یک واژه کلیدی.
پیمانه تحلیل نحوی جریانی از نشانهها را به درخت نحوی انتزاعی (AST) تبدیل میکند. بعضی از تحلیلگران نحوی حاوی دو پیمانهاند: پیمانه اول جریانی از نشانهها را میخواند وبرای هر ساختار نحوی که تشخیص داده میشود, تابعی را از پیمانه دوم فراخوانی میکند. توابع موجود در پیمانه دوم , گرههای AST را ایجاد کرده به هم پیوند میدهند. امتیاز این کار این است که میتوان با جایگزینی پیمانه تولید AST از یک تحلیل گر نحوی, AST دیگری را ایجادکرد , و از طرف دیگر , میتوان با جایگزیننی تحلیل گر نحوی, همان نوع AST را زا زبان دیگر به دست آورد.
پیمانه اداره کننده متن , اطلاعات مربوط به متنها را از نقاط مختلف برنامه جمع آوری میکند و گرهها را بر اساس این اطلاعات , حاشیه نویسی میکند. نمونههایی از این اطلاعات عبارتند از: اطلاعات نوع نشای از اعلان عبارات, اتصال دستوارت go to به برچسبهای آنها درزبانهای دستوری و در زبانهای توزیعی , تصمیم گیری در مورد این که کدام فراخوانی روالها ,محلی و کدامها از راه دور هستند. سپس این حاشیه نویسیها برای وارسی متن به کار میروند یا به پیمانههای دیگری ارسال میشوند (مثلا برای تولید کد)
پیمانه تولید کد میانی , ساختار های زبان را که در AST وجود دارند , به ساختارهای کلی تری تبدیل میکند این ساختارهای کلی , کد میانی را تولید میکنند که به طور خلاصه آن را IC مینامیم . طراح کامبایلر تصمیم میگیرد کد کدام ساختار , خاص زبان و کدام ساختار کلی است , اما معمولا این انتخاب چندان دشوار نیست . یک معیار برای سطح کد میانی این است که , تولید کد ماشین برای هر نوع ماشینی آسان باشد معمولا کد میانی متشکل از انحصار متقابل عبارات و دستور کنترل جریان است .
نمونههایی از ترجمههایی که توسط پیمانه تولید کد میانی انجام شده اند , عبارتند از : جایگزینی دستور while باشرطها ,برچشبها و پرشها در زبانهای دستوری در زبانهایی با انقیاد پویا کدی را درج میکند تا مشخص نماید که چه متدی از یک شیء باید فراخوانی شود, قرار دادن روالی به جای قاعده پرولوگ جستجوی عقبگرد را انجام میده . در هر یک از این موارد , روش دیگر این است که در سیستم زمان اجرا, روالی را با پارامترهای مناسبی فراخوانی کرد (سیستمهای زمان اجرا را در ادامه بررسی خواهیم کرد)
قاعده پرولوگ میتواند به شکل نمادی باقی بماند و توسط یک روال زمان اجرا تفسیر شود, روال زمان اجرا میتواند به طور پویا متدی را پیدا کند که فارخوانی شود. اگر شرط وبدنه دستور while به زیر روالی بی نام تبدیل شوند, این دستور میتواند توسط یک روال زمان اجرا انجام شود. پیمانه تولید کد میانی جایی است که در مورد انجام کار توسط کد درونی یا سیستم زمان اجرا تصمیم گیری میشود.
پیمانه بهینه سازی کد میانی , پیش پردازشهایی ار بر روی کد میانی انجام میدهد تا پیمانه تولید کد میانی کار آمد شود . نمونهای از یک پیش پردزاش ساده , بر چیدن ثوابت است که در ان , تمام عملیات موجود در عبارات که عملوندهای آن مشخص هستند, انجام میشوند مثال پیچیده تر , درونی سازی است که در آن , دستور فراخوانی تابع حذف شده به جای آن بدنه تابع قرار میگیرد.
پیمانه تولید کد , AST را به صورت یک لیست خطی از دستورات ماشین مقصد بازنویسی میکند . برای این کار , دستوراتی را برای بخشهایی از AST انتخاب میکند , ثباتهایی را باری نگهداری دادهها تخصیص میدهد و این دستورات را به ترتیب مناسبی میآراید.
پیمانه بهینه سازی که مقصد,لیستی از دستورات نمادی ماشین را در نظر میگیرد وسعی میکنند آن را بهینه سازی نماید. برای این منظور , به جای دنبالهای از دستورات ماشین , دنبالههای سریعتر یا کوتاهتری را قرار میدهد . این پیمانه , از ویژگیهای ماشین مقصد استفاده میکند.
مرزهای دقیقی بین بهینهسازی کد میانی, تولید کد , وبهینه سازی کدمقصد وجود ندارد . اگر تولید کد به خوبی صورت گیرد, در بهینه سازی کد مقصد کار زیادی انجام نمیشود , بر چیدن ثوابت میتواند در اثنای تولید کد یا کد مقصد انجام گیرد . علاوه براین بعضی از بهینه سازیها در یک پیمانه نسبت به پیمانه دیگر بهتر انجام میشودو تفکیک سه سطح فوق , اهمیت دارد.
پیمانه تولید کد ماشین , دستورات نمادی ماشین را به الگوهای بیتی متناظر تبدیل میکند. آدرس ماشین کد برنامه و دادهها را تعیین و جداول ثابت و جداول جابهجایی را تولید مینماید.
پیمانه خروجی کد اجرایی , دستورات ماشین کد شده , جداول ثابت , جداول جابهجایی , سرآیندها , پس آیندها و سایر موارد مورد نیاز را توسط سیستم عمل در فایل کد اجرایی ترکیب میکند.
ساختار کامپایلر
منابع :
http://daneshnameh.roshd.ir
http://www.sohacomputer.blogfa.com