کامپایل یا تدوین برنامهها (Compiling Programs)

در این درس یاد میگیریم که چگونه یک کد منبع (Source Code) برنامهای را به یک برنامه اجرایی و قابل استفاده تبدیل کنیم.
دسترسی به کد منبع یکی از بزرگترین آزادیهایی است که در دنیای متنباز لینوکس وجود دارد. کل اکوسیستم توسعه لینوکس متکی به همین آزادی تبادلات کد منبع بین توسعهدهندگان نرمافزاری میباشد.
کامپایل کردن برای بسیاری از کاربران Desktop، هنری گمشده است! پیشتر کامپایل کردن یگ برنامه کاری رایج بود، ولی امروزه توزیعکنندگان لینوکس مخازن عظیمی از کدهای از قبل کامپایل شده را در اختیار کاربران دسکتاپ قرار میدهند تا بهراحتی بتوان آنها را دانلود و سپس نصب نمود. با همه این توصیفات، چرا باید چگونگی کامپایل کردن یک برنامه را یاد بگیریم؟ به دو دلیل:
- دسترسی: ورای خیل عظیم برنامههای از قبل کامپایل شدهای که توزیعکنندگان در اختیار کاربران قرار میدهند، ممکن است توزیعکنندگان اپلیکیشنهای دلخواه شما را در توزیع مربوطه قرار نداده باشند. از این منظر، تنها راه ممکن برای دسترسی به برنامه مورد نظر، کامپایل کردن آن از کد منبع (Source Code) میباشد.
- بهروز بودن: درحالیکه برخی از توزیعکنندگان، اپلیکیشنها و تکنولوژیهای جدیدی را به توزیع خود اضافه میکنند، ولی همیشه اینطور نیست. برنامههای جدید، قبل از عیبیابی کامل ممکن است در داخل یک توزیع و حتی مخازن لب مرزی هم قرار داده نشود؛ پس به این منبع نیاز دارید تا برنامه مورد نظر خود را از کد منبع بر روی توزیع خود کامپایل کنید.
کامپایل کردن (Compile) چیست؟
Compiling بهمعنای گردآوری و تدوین میباشد و به پروسه ترجمه کد منبع (کد برنامهای که توسط برنامهنویس نوشته شده) به زبان قابل اجرا و پردازش توسط پردازشگر کامپیوتر میگویند.
پردازشگر کامپیوتر یا همان CPU در سطح بسیار ابتدایی کار میکند؛ در نتیجه برنامهها را در سطح برنامهنویسی که زبان ماشین نام دارد، اجرا میکند. درواقع زبان ماشین کد عددی صفر و یک است که کدهای صفر و یک عملیات در داخل پردازشگر و حافظه Memory را تعریف میکنند. برنامههای اولیه کامپیوتر به زبان صفر و یک نوشته میشدند.
این مشکل با اختراع زبان اسمبلی برطرف شد که زبان اسمبلی جایگزین کدهای عددی صفر و یک شد. برنامههایی که به زبان اسمبلی نوشته میشدند، توسط برنامهای با نام اسمبل پردازش میشدند. امروزه هنوز هم زبان اسمبلی برای وظایف برنامهنویسی ویژهای استفاده میشود (مانند نوشتن درایورها، دیوایسها و کدهای گنجانده شده در سیستمها)
پس از اسمبلی به سمت برنامهنویسی سمت بالا، حرکت شد. این زبانها به این دلیل، سطح بالا خوانده میشوند که برنامهنویس را قادر میسازد که کمتر نگران جزئیات پردازش در پردازشگر و حل مشکلات آن باشند.
اولین زبانهای برنامهنویسی سطح بالا در سال ۱۹۵۰ نوشته شدند که شامل Fortran (طراحی شده برای وظایف علمی و فنی) و Cobol (طراحی شده برای اپلیکیشنهای تجاری) بودند. هر دو این زبانها هنوز بسیار نسبت به استفاده امروزی محدود بودند.
درحالیکه زبانهای برنامهنویسی زیادی وجود داشت، دو مورد از آنها بر دیگران برتری داشتند. اکثر برنامههایی که برای سیستمهای مدرن نوشته میشدند به یکی از دو زبان C و یا C++ نوشته میشدند.
در مثالی که در ادامه، عنوان خواهیم کرد، برنامهای است که با زبان برنامهنویسی C نوشته شده است که آن را کامپایل خواهیم کرد.
برنامههایی که به زبانهای برنامهنویسی سطح بالا نوشته میشوند، قبل از اجرا (با پردازش در داخل برنامهای دیگر که کامپایلر نام دارد) به زبان ماشین تبدیل میگردند. برخی کامپایلرها، دستورالعملهای سطح بالا را به زبان اسمبلی ترجمه میکنند و سپس با استفاده از اسمبلر، ترجمه نهایی برای استفاده در پردازشگر انجام میشود.
پروسهای که اغلب در اتصال با کامپایل کردن، انجام میشود را لینک کردن (Linking) مینامند. برنامهها وظایف زیادی را انجام میدهند؛ برای نمونه فایلی را باز میکنند. بسیاری از برنامهها این کار را انجام میدهند ولی باز کردن یک فایل و کارهایی از این دست، توسط هر برنامه، اتلاف منابع سیستم بوده و بهتر است که برنامهای وجود داشته باشد که بداند چگونه فایلی را باز نماید و بین همه برنامههایی که به این عملیات نیاز دارند، اشتراک یابد.
ارایه پشتیبانی برای وظایف رایج، توسط Libraryها (بهمعنای کتابخانهها که همان فایلهای کتابخانهای لینوکس هستند) انجام میشود. این کتابخانهها حاوی چندین وظیف روتین و روزمره بوده و هر کدام، مجموعه وظایف رایجی را انجام میدهند که بسیاری از برنامهها میتوانند آن را بهاشتراک بگذارند.
اگر که به دایرکتوریهای /lib و /usr/lib نگاهی بیندازیم، میتوانیم ببینیم که بسیاری از آنها در کجا قرار دارند. برنامهای با نام لینکر (Linker) بهمنظور شکلدهی به اتصالات بین روجی کامپایلر و کتابخانهها استفاده میشود. نتیجه نهایی این پروسه اجرای فایل برنامه که بهمنظور استفاده آماده میشود، خواهد بود.
آیا همه برنامهها کامپایل شدهاند؟
همانگونه که دیدیم برخی برنامهها مانند اسکریپتهای پوسته (Shell Script) نیاز به کامپایل شدن ندارند، ولی بهصورت مستقیم اجرا میشوند. دلیل آن این است که این برنامهها توسط زبانها تفسیری یا اسکریپتی نوشته میشوند. این برنامهها که سالها رشد بسیاری داشتهاند، شامل Perl، Python، PHP، Ruby و … هستند.
زبانهای اسکریپتی توسط یک برنامه ویژه با نام مفسر اجرا میشود. یک مفسر، فایل برنامه را وارد میکند و محتویات آن را میخواند و هر یک از دستورالعملهای موجود در فایل را اجرا میکند. بهصورت کلی، برنامههای تفسیری بسیار کندتر از برنامههای کامپایل شده، اجرا میشوند.
دلیل این امر، آن است که کد منبع دستورالعملها در هر بار اجرا ترجمه شده درحالیکه یک برنامه کامپایل شده کد منبع، یکبار ترجمه میشود و این ترجمه بهصورت پایار در داخل فایل اجرایی ضبط میشود.
بنابراین چرا زبانهای تفسیری رایج هستند؟ برای انجام کارهای برنامهنویسی زیادی که به اندازه کافی سریع هستند، ولی فایده اصلی این زبانهای تفسیری چیست؟
سرعت بالای زبانهای تفسیری در سریع بودن و آسانتر بودن توسعه آنها توسط برنامهنویس است. برنامهها معمولا در یک چرخه زندگی کد (از زمانی که پروژه آغاز میشود تا زمانی که تحویل مشتری داده و باز به فاز اول برگشته تا اشکالها و بهروزرسانیها انجام شود) تکراری دارند.
زمانی که برنامهای از نظر حجم و اندازه گسترش مییابد، فاز کامپایل میتواند بسیار طولانی شود. درحالیکه زبانهای تفسیری با حذف این فاز، سرعت توسعه برنامه را بالا میبرند.
کامپایل کردن یک برنامه C
اکنون زمان آن رسیده که برنامهای را کامپایل کنیم. پیش از انجام کار، نیاز به ابزارهایی مانند compiler، linker و make داریم. کامپایلر زبان C تقریبا بهصورت سراسری در محیطهای لینوکسی استفاده شده که gcc (برگرفته از عبارت GNU C Compiler) نام دارد و توسط ریچارد استالمن (Richard Stallman) نوشته شده است. اکثر توزیعها بهطور پیشفرض دارای gcc نیستند. با فرمان زیر میتوانیم ببینیم که آیا کامپایلر موجود است یا خیر:
1 2 |
[me@linuxbox ~]$ which gcc /usr/bin/gcc |
همانگونه که میبینید ابزار gcc بهصورت پیشفرض موجود بوده و نصب شده است.
در ادامه چهار مرحله را دنبال خواهیم کرد:
- بهدست آوردن کد منبع
- آزمون ساختار درختی کد منبع
- ایجاد برنامه
- نصب برنامه
گام اول) بهدست آوردن کد منبع
ممکن است در حا حاضر، کد منبع برنامه خاصی را در اختیار داشته باشید. قاعدتا میتوانید از آن استفاده نمایید. اما بهمنظور تکمیل آموزش، نیاز است تا نحوه بهدست آوردن کد منبع برنامهای خاص را توضیح دهیم. بدین منظور برنامهای از پروژه GNU را تحت عنوان diction را انتخاب میکنیم. این برنامه کوچک، فایلهای متنی را برای کیفیت نوشتن و استایل بررسی میکند.
در ادامه بایستی دایرکتوری را برای کد منبع با نام src ایجاد کنیم. سپس با کمک فرمان cd وارد این دایرکتوری میشویم و در نهایت به سرور ftp.gnu.org متصل میشویم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
[me@linuxbox ~]$ mkdir src [me@linuxbox ~]$ cd src [me@linuxbox src]$ ftp ftp.gnu.org Connected to ftp.gnu.org. 220 GNU FTP server ready. Name (ftp.gnu.org:me): anonymous 230 Login successful. Remote system type is UNIX. Using binary mode to transfer files. ftp> cd gnu/diction 250 Directory successfully changed. ftp> ls 200 PORT command successful. Consider using PASV. 150 Here comes the directory listing. -rw-r--r-- 1 1003 65534 68940 Aug 28 1998 diction-0.7.tar.gz -rw-r--r-- 1 1003 65534 90957 Mar 04 2002 diction-1.02.tar.gz -rw-r--r-- 1 1003 65534 141062 Sep 17 2007 diction-1.11.tar.gz 226 Directory send OK. ftp> get diction-1.11.tar.gz local: diction-1.11.tar.gz remote: diction-1.11.tar.gz 200 PORT command successful. Consider using PASV. 150 Opening BINARY mode data connection for diction-1.11.tar.gz (141062 bytes). 226 File send OK. 141062 bytes received in 0.16 secs (847.4 kB/s) ftp> bye 221 Goodbye. [me@linuxbox src]$ ls diction-1.11.tar.gz |
همانطور که در تصویر مشاهده میکنید، فایلهای کد منبع معمولا بهصورت یک فایل فشرده با فرمت tar موجودند. این فایل حاوی ساختار درختی و سلسلهمراتبی دایرکتوریها و فایلهای مورد نیاز است که برنامه در آن نوشته شده است.
پس از آنکه فایل tar دانلود شده، بایستی آن را از حالت بستهبندی خارج کنیم. بدین منظور از فرمان tar و گزینه –xzf استفاده میکنیم:
1 2 3 |
[me@linuxbox src]$ tar xzf diction-1.11.tar.gz [me@linuxbox src]$ ls diction-1.11 diction-1.11.tar.gz |
اکنون که فرمان ls را اجرا میکنیم، علاوه بر فایل فشرده tar، یک دایرکتوری که از حالت بستهبندی خارج شده نیز وجود دارد.
گام دوم) آزمون ساختار درختی کد منبع
در این مرحله، ساختار درختی کد را مورد آزمون قرار خواهیم داد. هنگامی که فایل فشرده tar که حاوی کد منبع است، از حالت فشرده خارج میشود، دایرکتوری جدیدی با نام diction-1.11 که حاوی کد منبع است، ایجاد میشود. نگاهی به درون این دایرکتوری میاندازیم:
1 2 3 4 5 6 7 8 9 10 11 |
[me@linuxbox src]$ cd diction-1.11 [me@linuxbox diction-1.11]$ ls config.guess diction.c getopt.c nl config.h.in diction.pot getopt.h nl.po config.sub diction.spec getopt_int.h README configure diction.spec.in INSTALL sentence.c configure.in diction.texi.in install-sh sentence.h COPYING en Makefile.in style.1.in de en_GB misc.c style.c de.po en_GB.po misc.h test diction.1.in getopt1.c NEWS |
در این دایرکتوری، تعدادی فایل را مشاهده میکنیم، شامل برنامههایی که به پروژه GNU تعلق دارند و همچنین دیگر برنامهها، فایلهای README، INSTALL، NEWS و COPYING که این فایلها حاوی توضیحاتی درباره برنامه و اطلاعاتی در رابطه با نحوه ایجاد و نصب آن و همچنین قوانین کپیرایت هستند.
فایلهای دیگری نیز در این دایرکتوری وجود دارند که به پسوندهای c و h ختم میشوند:
1 2 3 4 |
[me@linuxbox diction-1.11]$ ls *.c diction.c getopt1.c getopt.c misc.c sentence.c style.c [me@linuxbox diction-1.11]$ ls *.h getopt.h getopt_int.h misc.h sentence.h |
فایلهای با پسوند c حاوی دو برنامه C میباشند که توسط بسته تهیه شده است و به دو ماژول تقسیم میشوند. این امر همیشه ایده خوبی است که برای مدیریت بهتر و آسانتر فایلهای بزرگ، برنامه به قطعات کوچکتر تقسیم شود.
فایلهای با پسوند h فایلهای هدر بوده و شامل متن ساده هستند. فایلهای هدر حاوی توضیحات روزمرهای بوده که در کد منبع گنجانده شدهاند. برای اینکه کامپایلر به ماژولها متصل شود، بایستی توضیحی از کلیه ماژولهای مورد نیاز دریافت کند تا کل برنامه کامل شود.
در ابتدای فایل diction.c خط زیر را مشاهده میکنیم. این خط به کامپایلر میگوید که فایل getopt.h را بخواند. برخی عبارات خواندنی دیگر نیز وجود دارند.
1 |
#include "getopt.h" |
گام سوم) ایجاد برنامه
اکثر برنامهها با دو فرمان ساده زیر اجرا میشوند:
1 2 |
./configure make |
برای اجرا نیاز است که پیش از configure عبارت ./ را قرار دهیم تا مسیر صحیح دایرکتوری فعلی را نشان دهد:
1 |
[me@linuxbox diction-1.11]$ ./configure |
این نکته حائز اهمیت است که بررسی کنید و ببینید که فرمان بدون خطا اجرا شده است. اگر خطایی رخ داده باشد، پیکربندی با شکست مواجه خواهد شد و برنامه تا زمانی که خطاها برطرف نشود، ایجاد نخواهد شد.
پس از پایان بررسیها، فایلهایی ایجاد میشوند که مهمترین آنها Makefile میباشد. Makefile یک فایل پیکربندی است که به برنامه make اعلام مینماید که دقیقا چگونه برنامه را ایجاد کند.
درصورتیکه خطایی وجود ندارد، بهمنظور ایجاد برنامه، فرمان make را وارد کنید. فرمان make با استفاده از محتویات فایل Makefile عملیات مورد نیاز را انجام میدهد. پس از اجرای فرمان، پیامهای زیادی را مشاهده خواهید کرد.
1 |
[me@linuxbox diction-1.11]$ make |
پس از پایان از دایرکتوری فعلی، یک لیست دریافت کنید:
1 2 3 4 5 6 7 8 9 10 11 12 |
[me@linuxbox diction-1.11]$ ls config.guess de.po en install-sh sentence.c config.h diction en_GB Makefile sentence.h config.h.in diction.1 en_GB.mo Makefile.in sentence.o config.log diction.1.in en_GB.po misc.c style config.status diction.c getopt1.c misc.h style.1 config.sub diction.o getopt1.o misc.o style.1.in configure diction.pot getopt.c NEWS style.c configure.in diction.spec getopt.h nl style.o COPYING diction.spec.in getopt_int.h nl.mo test de diction.texi getopt.o nl.po de.mo diction.texi.in INSTALL README |
در این بین، فایلهای موجود diction و style (برنامهای که قرار بود ایجاد کنیم) را مشاهده مینمایید.
تبریک! شما اولین برنامههای خود را کامپایل کردید. ولی برای آنکه مطمئن شویم، یکبار دیگر فرمان make را اجرا میکنیم:
1 2 |
[me@linuxbox diction-1.11]$ make make: Nothing to be done for `all'. |
این بار یک پیام عجیب ارسال میشود. فرمان make بهجای اینکه دوباره همه چیز را از اول ایجاد نماید، فقط موارد مورد نیاز را ایجاد میکند و از آنجایی که همه موارد مورد نیاز ایجاد شدهاند، هیچ کاری نمیکند و پیامی را که که معنای آن این است که «کاری برای انجام باقی نمانده» نمایش میدهد.
حتی میتوانید برای تست آن با استفاده از فرمان rm یکی از فایلهای موجود (مثلا getopt.o) را حذف کرده و مجددا فرمان make را اجرا نمایید.
گام چهارم) نصب برنامه
کد منبعی که بهخوبی بستهبندی شده باشد، معمولا حاوی یک فایل ایجاد تحت عنوان install میباشد. این فایل هدف برنامه نهایی را با استفاده از بستهای که کامپایل شده بر روی دایرکتوری مربوطه (معمولا /usr/local/bin) نصب میکند. به این دلیل که این دایرکتوری برای کاربران عادی قابل دسترسی و تغییر نیست و پیش از اجرا، بایستی دسترسی کاربر ارشد (Super User) را داشته باشید:
1 |
[me@linuxbox diction-1.11]$ sudo make install |
برای اطمینان، میتوانیم با استفاده از فرمان which از نصب برنامه diction مطئمن شویم یا با فرمان man صفحه برنامه diction را مشاهده کنیم:
1 2 3 |
[me@linuxbox diction-1.11]$ which diction /usr/local/bin/diction [me@linuxbox diction-1.11]$ man diction |