مزایا و معایب روش های مختلف حمل و نقل. حمل و نقل لجستیک. حمل و نقل جاده ای بین المللی

برای استفاده صحیح از gcc، کامپایلر استاندارد C برای لینوکس، باید گزینه های خط فرمان را یاد بگیرید. علاوه بر این، gcc زبان C را گسترش می دهد. حتی اگر قصد دارید کد منبع خود را در استاندارد ANSI آن زبان بنویسید، برخی از پسوندهای gcc وجود دارد که برای درک فایل های هدر لینوکس باید آنها را بدانید.

اکثر گزینه های خط فرمان همان گزینه هایی هستند که توسط کامپایلرهای C استفاده می شود. هیچ استانداردی برای برخی از گزینه ها وجود ندارد. در این فصل به مهم ترین گزینه هایی که در برنامه نویسی روزمره استفاده می شوند اشاره خواهیم کرد.

تلاش برای انطباق با استاندارد ISO C مفید است، اما از آنجایی که C یک زبان سطح پایین است، شرایطی وجود دارد که ویژگی های استاندارد به اندازه کافی رسا نیستند. دو حوزه وجود دارد که از برنامه های افزودنی gcc به طور گسترده استفاده می شود: تعامل با کد اسمبلی (این موضوعات در http://www.delorie.com/djgpp/doc/brennan/ پوشش داده شده است) و ساخت کتابخانه های مشترک (به فصل 8 مراجعه کنید). از آنجایی که فایل‌های هدر بخشی از کتابخانه‌های مشترک هستند، برخی پسوندها نیز در فایل‌های هدر سیستم ظاهر می‌شوند.

البته، افزونه های بسیار بیشتری وجود دارند که در هر نوع برنامه نویسی دیگری مفید هستند که واقعاً می توانند به کدنویسی کمک کنند. اطلاعات بیشتر در مورد این برنامه‌های افزودنی را می‌توانید در مستندات gcc Texinfo بیابید.

5.1. گزینه های gcc

gcc گزینه های دستوری زیادی را می پذیرد. خوشبختانه مجموعه گزینه هایی که واقعاً باید از آنها آگاه باشید چندان بزرگ نیستند و در این فصل به بررسی آن خواهیم پرداخت.

بیشتر گزینه‌ها مشابه یا مشابه گزینه‌های سایر کامپایلرها هستند، gcc شامل اسناد عظیمی از گزینه‌های آن است که از طریق info gcc در دسترس است (صفحه gcc man نیز این اطلاعات را ارائه می‌دهد، اما صفحات man به اندازه اسناد Texinfo به‌روزرسانی نمی‌شوند. ).

-o نام فایل نام فایل خروجی را مشخص می کند. این معمولاً هنگام کامپایل کردن در یک فایل شی ضروری نیست، به عنوان مثال، پیش فرض جایگزینی filename.c با filename.o است. با این حال، اگر یک فایل اجرایی ایجاد کنید، به طور پیش فرض (به دلایل تاریخی) با نام a.out ایجاد می شود. این همچنین زمانی مفید است که می خواهید فایل خروجی را در دایرکتوری دیگری قرار دهید.
-با بدون پیوند دادن فایل منبع مشخص شده در خط فرمان، کامپایل می کند. در نتیجه، یک فایل شی برای هر فایل منبع ایجاد می شود. هنگام استفاده از make، کامپایلر gcc معمولا برای هر فایل شیء فراخوانی می شود. به این ترتیب، در صورت بروز خطا، پیدا کردن فایلی که کامپایل نشده است آسان تر است. با این حال، اگر دستورات را با دست تایپ می کنید، غیر معمول نیست که چندین فایل در یک تماس gcc مشخص شوند. اگر ممکن است هنگام تعیین چندین فایل در خط فرمان ابهامی ایجاد شود، بهتر است فقط یک فایل را مشخص کنید. به عنوان مثال، به جای gcc -c -o a.o a.c b.c، منطقی است که از gcc -c -o a.o b.c استفاده کنید.
-دفو ماکروهای پیش پردازنده را در خط فرمان تعریف می کند. ممکن است لازم باشد نویسه هایی را که پوسته به عنوان کاراکترهای خاص در نظر می گیرد، کنار بگذارید. برای مثال، هنگام تعریف رشته، باید از استفاده از کاراکترهای پایان‌دهنده رشته خودداری کنید. دو روش متداول عبارتند از: -Dfoo="bar"" و -Dfoo=\"bar\". راه اول بسیار بهتر عمل می‌کند اگر فضاهایی در رشته وجود دارد، زیرا پوسته با فضای سفید به روش خاصی برخورد می کند.
من دایرکتوری دایرکتوری را به فهرست دایرکتوری ها برای جستجوی فایل های شامل اضافه می کند.
فهرست -L دایرکتوری را به فهرست دایرکتوری ها برای جستجوی کتابخانه ها اضافه می کند، gcc کتابخانه های مشترک را بر کتابخانه های ایستا ترجیح می دهد مگر اینکه خلاف آن مشخص شده باشد.
-من فک میکنم پیوندها علیه کتابخانه lib foo. مگر اینکه طور دیگری مشخص شده باشد، gcc پیوند در برابر کتابخانه های مشترک (lib foo .so) را به کتابخانه های ثابت (lib foo .a) ترجیح می دهد. پیوند دهنده توابع را در تمام کتابخانه های فهرست شده به ترتیب فهرست شده جستجو می کند. جستجو وقتی تمام می شود که تمام عملکردهای مورد نیاز پیدا شوند.
-استاتیک پیوندها فقط با کتابخانه های ثابت. فصل 8 را ببینید.
-g، -ggdb شامل اطلاعات اشکال زدایی است. گزینه -g باعث می‌شود gcc اطلاعات استاندارد اشکال‌زدایی را در بر بگیرد. گزینه -ggdb مشخص می کند که شامل شود مقدار زیادیاطلاعاتی که فقط دیباگر gdb می تواند بفهمد.
اگر فضای دیسک محدود است یا می‌خواهید برخی از عملکردها را فدای سرعت پیوند کنید، باید از -g استفاده کنید. در این مورد، ممکن است لازم باشد از یک دیباگر غیر از gdb استفاده کنید. برای کامل‌ترین اشکال‌زدایی، باید -ggdb را مشخص کنید. در این صورت gcc تا حد امکان آماده می شود اطلاعات دقیقبرای gdb . لازم به ذکر است که برخلاف اکثر کامپایلرها، gcc برخی از اطلاعات اشکال زدایی را در کد بهینه شده قرار می دهد. با این حال، ردیابی در اشکال‌زدای کدهای بهینه‌سازی شده می‌تواند مشکل باشد، زیرا زمان اجرا می‌تواند بخش‌هایی از کد را که انتظار دارید اجرا شود پرش کرده و رد شود. با این حال، امکان گرفتن وجود دارد عملکرد خوبدر مورد اینکه چگونه بهینه سازی کامپایلرها نحوه اجرای کد را تغییر می دهد.
-O، -روشن باعث می شود gcc کد را بهینه کند. به طور پیش فرض، gcc مقدار کمی بهینه سازی را انجام می دهد. هنگام تعیین یک عدد (n)، بهینه سازی در یک سطح مشخص انجام می شود. رایج ترین سطح بهینه سازی 2 است. در حال حاضر بالاترین سطح بهینه سازی در نسخه استاندارد gcc 3 است. توصیه می کنیم از -O2 یا -O3 استفاده کنید. -O3 می تواند اندازه برنامه را افزایش دهد، بنابراین اگر مهم است هر دو را امتحان کنید. اگر حافظه و فضای دیسک برای برنامه شما مهم است، می‌توانید از گزینه -Os نیز استفاده کنید، که اندازه کد را به قیمت افزایش زمان اجرا به حداقل می‌رساند. gcc تنها زمانی که حداقل بهینه سازی (-O) اعمال شده باشد، داخلی ها را فعال می کند.
-ansi پشتیبانی در برنامه های C از تمام استانداردهای ANSI (X3.159-1989) یا معادل ISO آنها (ISO/IEC 9899:1990) (که معمولاً به عنوان C89 یا کمتر C90 نامیده می شود). توجه داشته باشید که این امر مطابقت کامل با استاندارد ANSI/ISO را فراهم نمی کند.
گزینه -ansi پسوندهای gcc را که معمولاً با استانداردهای ANSI/ISO در تضاد هستند غیرفعال می کند. (با توجه به این واقعیت که این پسوندها توسط بسیاری از کامپایلرهای C دیگر پشتیبانی می شوند، این در عمل مشکلی ایجاد نمی کند.) همچنین ماکرو __STRICT_ANSI__ را تعریف می کند (همانطور که در ادامه این کتاب توضیح داده شد) که فایل های هدر برای پشتیبانی از ANSI/ISO مطابق با آن استفاده می کنند. محیط.
-آدم ملانطقی تمام اخطارها و خطاهای مورد نیاز استاندارد زبان ANSI/ISO C را نمایش می دهد. این امر مطابقت کامل با استاندارد ANSI/ISO را ارائه نمی دهد.
-دیوار تولید همه هشدارهای gcc را فعال می کند، که معمولا مفید است. اما این شامل گزینه هایی نمی شود که ممکن است در موارد خاص مفید باشند. سطح مشابهی از جزئیات برای تجزیه کننده پرز برای کد منبع شما تنظیم می شود، gcc به شما اجازه می دهد تا به صورت دستی هر هشدار کامپایلر را روشن و خاموش کنید. دفترچه راهنمای gcc تمام هشدارها را با جزئیات شرح می دهد.
5.2. فایل های هدر
5.2.1. طولانی طولانی

نوع long long نشان می دهد که طول بلوک حافظه حداقل به اندازه طول است. در اینتل i86 و دیگر پلتفرم‌های 32 بیتی، طول 32 بیت است، در حالی که طولانی 64 بیت است. در پلتفرم‌های 64 بیتی، اشاره‌گرها و طولانی‌ها 64 بیت طول می‌کشند، و طول می‌تواند بسته به پلتفرم، 32 یا 64 بیت باشد. نوع طولانی در استاندارد C99 (ISO/IEC 9899:1999) پشتیبانی می‌شود و یک پسوند C طولانی مدت است که توسط gcc ارائه شده است.

5.2.2. توابع داخلی

برخی از بخش‌های فایل‌های هدر لینوکس (به ویژه آنهایی که مختص یک سیستم خاص هستند) از توابع داخلی بسیار گسترده استفاده می‌کنند. آنها به سرعت ماکروها هستند (هزینه ای برای فراخوانی تابع ندارند) و انواع اعتبار سنجی را که در یک فراخوانی تابع معمولی موجود است را ارائه می دهند. کدی که توابع داخلی را فراخوانی می کند باید با حداقل حداقل بهینه سازی (-O) فعال کامپایل شود.

5.2.3. کلمات کلیدی توسعه یافته جایگزین

در gcc، هر کلمه کلیدی توسعه یافته (کلمات کلیدی که توسط استاندارد ANSI/ISO تعریف نشده اند) دو نسخه دارد: خودش کلمه کلیدیو یک کلمه کلیدی که از دو طرف با دو خط زیرین احاطه شده است. هنگامی که کامپایلر در حالت استاندارد استفاده می شود (معمولاً هنگامی که گزینه -ansi فعال است)، کلمات کلیدی توسعه یافته عادی شناسایی نمی شوند. بنابراین، برای مثال، کلمه کلیدی ویژگی در یک فایل هدر باید به صورت __خصیصه__ نوشته شود.

5.2.4. ویژگی های

کلمه کلیدی مشخصه توسعه یافته برای ارسال اطلاعات بیشتر در مورد یک تابع، متغیر یا نوع اعلام شده به gcc از آنچه توسط کد ANSI/ISO C مجاز است استفاده می شود. برای مثال، ویژگی aligned به gcc می گوید که دقیقا چگونه یک متغیر یا نوع را تراز کند. ویژگی packed نشان می دهد که هیچ بالشتکی استفاده نخواهد شد. noreturn مشخص می کند که این تابع هرگز برنمی گردد، که به gcc اجازه می دهد بهتر بهینه شود و از هشدارهای جعلی جلوگیری کند.

ویژگی های تابع با اضافه کردن آنها به اعلان تابع، به عنوان مثال:

void die_die_die(int، char*) __ویژگی__ ((__noreturn__));

یک اعلان ویژگی بین پرانتز و یک نقطه ویرگول قرار می گیرد و حاوی کلمه کلیدی ویژگی و به دنبال آن ویژگی های داخل پرانتز است. اگر ویژگی های زیادی وجود دارد، باید از یک لیست جدا شده با کاما استفاده شود.

printm(char*, ...)

ویژگی__((const,

قالب (printf, 1, 2)))؛

در این مثال، می بینید که printm هیچ مقداری غیر از مقادیر مشخص شده را در نظر نمی گیرد و هیچ عارضه جانبی مربوط به تولید کد (const) ندارد، printm نشان می دهد که gcc باید آرگومان های تابع را به همان روش printf() بررسی کند. استدلال ها آرگومان اول رشته قالب است و آرگومان دوم اولین پارامتر جایگزین (فرمت) است.

برخی از ویژگی ها با پیشرفت مواد مورد بحث قرار خواهند گرفت (به عنوان مثال، در طی توضیح ساخت کتابخانه های مشترک در فصل 8). اطلاعات جامع در مورد ویژگی ها را می توان در اسناد gcc در قالب Texinfo یافت.

گاهی اوقات ممکن است متوجه شوید که در حال جستجوی فایل های هدر لینوکس هستید. به احتمال زیاد تعدادی طرح را خواهید یافت که مطابق با ANSI/ISO نیستند. برخی از آنها ارزش بررسی را دارند. تمام ساختارهای پوشش داده شده در این کتاب با جزئیات بیشتری در مستندات gcc پوشش داده شده است.

گاهی اوقات ممکن است متوجه شوید که در حال جستجوی فایل های هدر لینوکس هستید. به احتمال زیاد تعدادی طرح را خواهید یافت که مطابق با ANSI/ISO نیستند. برخی از آنها ارزش بررسی را دارند. تمام ساختارهای پوشش داده شده در این کتاب با جزئیات بیشتری در مستندات gcc پوشش داده شده است.

اکنون که کمی در مورد استاندارد C آموخته اید، بیایید نگاهی به گزینه هایی بیندازیم که کامپایلر gcc ارائه می دهد تا اطمینان حاصل شود که با استاندارد C زبانی که در آن می نویسید مطابقت دارد. سه راه برای اطمینان از سازگاری کد C شما با استانداردها و عاری از نقص وجود دارد: گزینه‌هایی که کنترل می‌کنند کدام نسخه استاندارد را می‌خواهید مطابقت داشته باشید، تعاریفی که فایل‌های هدر را کنترل می‌کنند، و گزینه‌های هشداری که باعث بررسی دقیق‌تر کد می‌شوند.

gcc دارای مجموعه عظیمی از گزینه ها است و در اینجا فقط مواردی را که مهمترین آنها را در نظر می گیریم پوشش می دهیم. فهرست کاملی از گزینه‌ها را می‌توانید در صفحات مرد آنلاین gcc پیدا کنید. همچنین به طور مختصر درباره برخی از گزینه های #define Directive که می توان از آنها استفاده کرد صحبت خواهیم کرد. آنها معمولاً باید قبل از هر خط #include در کد منبع شما مشخص شوند یا در خط فرمان gcc تعریف شوند. ممکن است از فراوانی گزینه ها برای انتخاب استاندارد به جای یک پرچم ساده برای اجبار به استفاده از استاندارد فعلی شگفت زده شوید. دلیل آن این است که بسیاری از برنامه های قدیمی بر رفتار کامپایلر تاریخی متکی هستند و برای به روز رسانی آنها به آخرین استانداردها به کار قابل توجهی نیاز دارند. به ندرت، اگر هرگز، بخواهید کامپایلر خود را به روز کنید تا کدهای در حال اجرا را خراب کند. با تغییر استانداردها، مهم است که بتوانیم بر خلاف یک استاندارد خاص کار کنیم، حتی اگر جدیدترین نسخه استاندارد نباشد.

حتی اگر یک برنامه کوچک برای استفاده شخصی می‌نویسید، جایی که رعایت استانداردها ممکن است چندان مهم نباشد، اغلب منطقی است که هشدارهای gcc اضافی را اضافه کنید تا کامپایلر را مجبور کنید قبل از اجرای برنامه به دنبال خطا در کد شما بگردد. این همیشه کارآمدتر از عبور از کد در دیباگر و تعجب کردن از کجاست. کامپایلر گزینه های زیادی دارد که فراتر از بررسی استانداردهای ساده است، مانند توانایی تشخیص کدی که با استاندارد مطابقت دارد اما احتمالاً معنایی مشکوک دارد. به عنوان مثال، یک برنامه ممکن است یک دستور اجرا داشته باشد که اجازه می دهد تا یک متغیر قبل از مقداردهی اولیه به آن دسترسی پیدا کند.

اگر نیاز به نوشتن برنامه ای برای استفاده مشترک دارید، با توجه به درجه انطباق و انواع هشدارهای کامپایلر که فکر می کنید کافی هستند، بسیار مهم است که کمی تلاش کنید و کد خود را بدون هیچ هشداری کامپایل کنید. اگر برخی هشدارها را بپذیرید و عادت کنید که آنها را نادیده بگیرید، یک روز ممکن است هشدار جدی تری ظاهر شود که خطر از دست دادن آن را دارید. اگر کد شما همیشه بدون پیام‌های هشدار کامپایل می‌شود، هشدار جدیدی به‌طور قطع مورد توجه شما قرار می‌گیرد. کامپایل کردن کد بدون اخطار عادت خوبی است.

گزینه های کامپایلر برای استانداردهای ردیابی

Ansi مهمترین گزینه در مورد استانداردها است و باعث می شود کامپایلر مطابق استاندارد زبان ISO C90 عمل کند. برخی از برنامه های افزودنی gcc غیر منطبق با استاندارد را غیرفعال می کند، نظرات به سبک C++ (//) را در برنامه های C غیرفعال می کند، و مدیریت سه گراف های ANSI (توالی های سه نویسه) را فعال می کند. علاوه بر این، حاوی ماکرو __STRICT_ANSI__ است که برخی از پسوندها را در فایل‌های هدر غیرفعال می‌کند که با استاندارد سازگار نیستند. در نسخه های بعدی کامپایلر، استاندارد پذیرفته شده ممکن است تغییر کند.

Std= - این گزینه با ارائه پارامتری که دقیقاً مشخص می کند چه استانداردی مورد نیاز است، کنترل دقیق تری بر روی اینکه کدام استاندارد باید استفاده شود را فراهم می کند. گزینه های اصلی موجود زیر هستند:

C89 - پشتیبانی از استاندارد C89؛

Iso9899:1999 - پشتیبانی از آخرین نسخه استاندارد ISO، C90.

Gnu89 - از استاندارد C89 پشتیبانی می کند اما برخی از برنامه های افزودنی گنو و برخی عملکردهای C99 را مجاز می کند. در نسخه 4.2 gcc این گزینه پیش فرض است.

گزینه‌هایی برای استاندارد ردیابی در دستورالعمل‌ها

ثابت هایی وجود دارند (#defines) که می توانند به عنوان گزینه هایی در خط فرمان یا به عنوان تعاریف در کد منبع برنامه مشخص شوند. ما به طور کلی فکر می کنیم که آنها از خط فرمان کامپایلر استفاده می کنند.

STRICT_ANSI__ استاندارد ISO C را مجبور به استفاده می کند. زمانی که گزینه -ansi در خط فرمان کامپایلر داده می شود، تعریف می شود.

POSIX_C_SOURCE=2 - عملکرد تعریف شده توسط IEEE Std 1003.1 و 1003.2 را فعال می کند. بعداً در این فصل به این استانداردها باز خواهیم گشت.

BSD_SOURCE - عملکرد سیستم های BSD را فعال می کند. اگر با تعاریف POSIX مغایرت داشته باشند، تعاریف BSD اولویت دارند.

GNU_SOURCE - به طیف گسترده ای از ویژگی ها و توابع، از جمله پسوندهای گنو اجازه می دهد. اگر این تعاریف با تعاریف POSIX مغایرت داشته باشند، تعاریف دوم اولویت دارند.

گزینه های کامپایلر برای خروجی هشدارها

این گزینه ها از خط فرمان به کامپایلر ارسال می شوند. و دوباره، ما فقط موارد اصلی را فهرست می کنیم، لیست کاملرا می توان در راهنمای راهنمای آنلاین gcc یافت.

Pedantic قدرتمندترین گزینه پاکسازی برای کد C است. علاوه بر فعال کردن گزینه بررسی در برابر استاندارد C، برخی از ساختارهای سنتی C را که توسط استاندارد ممنوع هستند غیرفعال می‌کند و همه پسوندهای گنو را غیرقانونی از استاندارد می‌کند. از این گزینه باید استفاده شود تا کد C شما تا حد امکان قابل حمل باشد. عیب آن این است که کامپایلر به تمیزی کد شما بسیار اهمیت می دهد و گاهی اوقات شما مجبور می شوید مغز خود را برای خلاص شدن از شر چند اخطار باقیمانده در نظر بگیرید.

Wformat - صحت انواع آرگومان های توابع خانواده printf را بررسی می کند.

Wparentheses - وجود پرانتز را حتی در جاهایی که نیازی به آنها نیست بررسی می کند. این گزینه برای بررسی اینکه آیا ساختارهای پیچیده همانطور که در نظر گرفته شده مقداردهی اولیه شده اند بسیار مفید است.

wswitch-default - وجود نوع پیش‌فرض را در دستورات سوئیچ بررسی می‌کند، که به طور کلی سبک برنامه‌نویسی خوبی در نظر گرفته می‌شود.

Wunused - موارد مختلف را بررسی می کند، مانند توابع استاتیک اعلام شده اما اعلام نشده، پارامترهای استفاده نشده، نتایج دور ریخته شده.

دیوار - اکثر انواع هشدار gcc را فعال می کند، از جمله تمام گزینه های قبلی -W (فقط -pdantic پوشش داده نمی شود). با کمک آن به راحتی می توان به پاکی کد برنامه دست یافت.

توجه داشته باشید

گزینه های هشدار پیشرفته بسیار بیشتری وجود دارد، برای همه جزئیات به صفحات وب gcc مراجعه کنید. به طور کلی، توصیه می کنیم از -Wall استفاده کنید. این یک سازش خوب بین بررسی کد برنامه است کیفیت بالا، و نیاز به کامپایلر برای خروجی بسیاری از اخطارهای بی اهمیت که باطل کردن آنها دشوار می شود.

GCC با هر توزیع گنجانده شده است لینوکسو معمولا به صورت پیش فرض تنظیم می شود. رابط GCC یک رابط کامپایلر استاندارد بر روی پلت فرم یونیکس است که در اواخر دهه 60 و اوایل دهه 70 قرن گذشته ریشه داشت - یک رابط خط فرمان. نترسید، در گذشته مکانیسم تعامل کاربر در این مورد به کمال ممکن رسیده است و با GCC کار کنید (با چند ابزار اضافی و خوب ویرایشگر متن) ساده تر از هر IDE بصری مدرن است. نویسندگان کیت سعی کردند تا حد امکان فرآیند کامپایل و مونتاژ برنامه ها را خودکار کنند. کاربر برنامه کنترل را فراخوانی می کند gcc، آرگومان های خط فرمان (گزینه ها و نام فایل ها) را تفسیر می کند و برای هر فایل ورودی، مطابق با زبان برنامه نویسی مورد استفاده، کامپایلر خود را اجرا می کند، سپس در صورت لزوم، gccبه طور خودکار اسمبلر و پیوند دهنده (لینکر) را فراخوانی می کند.

جالب اینجاست که کامپایلرها یکی از معدود برنامه های یونیکس هستند که به پسوند فایل اهمیت می دهند. با پسوند، GCC تعیین می کند که چه نوع فایلی در مقابل آن قرار دارد و چه نیازهایی (می توان) با آن انجام داد. فایل های منبع زبان سیباید پسوند .c را در زبان داشته باشد C++، به طور متناوب، .cpp، فایل های سرصفحه در زبان سیفایل های شی .h، .o و غیره. اگر از پسوند اشتباه استفاده می کنید، gccبه درستی کار نخواهد کرد (اگر اصلاً موافق انجام کاری هستید).

بیایید به سراغ تمرین برویم. بیایید یک برنامه ساده بنویسیم، کامپایل و اجرا کنیم. به عنوان فایل منبع یک برنامه نمونه در زبان، اصل نباشیم سیبیایید یک فایل با محتوای زیر ایجاد کنیم:

/* سلام سی */

#عبارتند از

اصلی( خالی )
{

Printf("Hello World\n");

برگشت 0 ;

اکنون در دایرکتوری c hello.c دستور را صادر می کنیم:

$ gcc hello.c

پس از چند کسری از ثانیه، فایل a.out در دایرکتوری ظاهر می شود:

$ls
a.out سلام.ج

این فایل اجرایی تمام شده برنامه ما است. پیش فرض gccنام a.out را به فایل اجرایی خروجی می دهد (روزی روزگاری به این معنی بود خروجی اسمبلر).

$file a.out
a.out: ELF 64 بیتی LSB قابل اجرا، x86-64، نسخه 1 (SYSV)، پیوندی پویا (از لبه های اشتراکی استفاده می کند)، برای گنو/لینوکس 2.6.15، بدون حذف

بیایید نتیجه را اجرا کنیم نرم افزار:

$ ./a.out
سلام دنیا


چرا لازم است مسیر فایل را در دستور run به صراحت مشخص کنیم تا فایلی از دایرکتوری فعلی اجرا شود؟ اگر مسیر فایل اجرایی به صراحت مشخص نشده باشد، پوسته، دستورات را تفسیر می کند، به دنبال فایل در دایرکتوری ها می گردد که لیست آنها توسط متغیر سیستم PATH مشخص شده است.

$ پژواک $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games

دایرکتوری ها در لیست با دو نقطه از هم جدا شده اند. هنگام جستجوی فایل‌ها، پوسته فهرست‌ها را به ترتیبی که فهرست شده‌اند نگاه می‌کند. به طور پیش فرض، به دلایل امنیتی، دایرکتوری فعلی است. در لیست گنجانده نشده است؛ بر این اساس، پوسته به دنبال فایل های اجرایی در آن نخواهد بود.

چرا تهیه آن توصیه نمی شود. در PATH؟ اعتقاد بر این است که در یک سیستم چند کاربره واقعی همیشه یک فرد بد وجود خواهد داشت که یک برنامه مخرب را در فهرست عمومی با نام فایل اجرایی که با نام برخی از دستوراتی که اغلب توسط یک مدیر محلی با superuser فراخوانی می شود مطابقت دارد، قرار می دهد. حقوق ... طرح موفق خواهد شد اگر . در ابتدای فهرست دایرکتوری قرار دارد.


سودمند فایلاطلاعات مربوط به نوع (از دیدگاه سیستم) فایل ارسال شده در خط فرمان را نمایش می دهد، برای برخی از انواع فایل ها انواع فایل ها را نمایش می دهد. اطلاعات اضافیدر رابطه با محتویات فایل

$file hello.c
hello.c: متن برنامه ASCII C
$file annotation.doc
annotation.doc: CDF V2 Document، Little Endian، سیستم عامل: Windows، نسخه 5.1، صفحه کد: 1251، نویسنده: MIH، الگو: Normal.dot، آخرین ذخیره شده توسط: MIH، شماره ویرایش: 83، نام ایجاد کننده برنامه: Microsoft Office Word، کل زمان ویرایش: 09:37:00، آخرین چاپ: پنجشنبه 22 ژانویه 07:31:00 2009، زمان/تاریخ ایجاد: دوشنبه 12 ژانویه 07:36:00 2009، آخرین زمان/تاریخ ذخیره شده: پنجشنبه 22 ژانویه 07:34:00 2009، تعداد صفحات: 1، تعداد کلمات: 3094، تعداد کاراکترها: 17637، امنیت: 0

این در واقع تمام چیزی است که برای برنامه موفق از کاربر لازم است gcc :)

نام فایل اجرایی خروجی (و همچنین هر فایل دیگری که توسط gcc) را می توان با تغییر داد گزینه -o:

$ gcc -o سلام hello.c
$ls
سلام سلام.ج
$ ./سلام
سلام دنیا


در مثال ما، تابع main() مقدار ظاهرا غیر ضروری 0 را برمی گرداند. در سیستم‌های یونیکس مانند، در پایان برنامه، مرسوم است که یک عدد صحیح به پوسته برگردانده شود - در صورت تکمیل موفقیت‌آمیز، صفر، و غیره. مفسر پوسته به طور خودکار مقدار حاصل را به متغیر محیطی به نام ? . می توانید محتویات آن را با استفاده از echo $ مشاهده کنید؟ :

$ ./سلام
سلام دنیا
$ اکو $؟
0

در بالا گفته شد gccیک برنامه کنترلی است که برای خودکارسازی فرآیند کامپایل طراحی شده است. بیایید ببینیم در نتیجه اجرای دستور gcc hello.c چه اتفاقی می افتد.

فرآیند کامپایل را می توان به 4 مرحله اصلی تقسیم کرد: پردازش پیش پردازنده، کامپایل واقعی، مونتاژ، پیوند (بایدینگ).

گزینه ها gccبه شما این امکان را می دهد که در هر یک از این مراحل فرآیند را قطع کنید.

پیش پردازنده فایل منبع را برای کامپایل آماده می کند - نظرات را قطع می کند، محتویات فایل های هدر را اضافه می کند (دستورالعمل پیش پردازنده #include )، توسعه ماکروها را پیاده سازی می کند (ثابت نمادی، دستورالعمل پیش پردازنده #define).

استفاده از فرصت گزینه -Eاقدامات بعدی gccمی توانید محتویات فایل پردازش شده توسط پیش پردازنده را قطع کرده و مشاهده کنید.

$ gcc -E -o hello.i hello.c
$ls
hello.c سلام.i
$ کمتر سلام.i
. . .
# 1 "/usr/include/stdio.h" 1 3 4
# 28 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/features.h" 1 3 4
. . .
typedef بدون امضا char __u_char;
typedef بدون علامت کوتاه int __u_short;
typedef بدون امضا int __u_int;
. . .
extern int printf (__const char *__restrict __format, ...);
. . .
شماره 4 "hello.c" 2
اصلی (باطل)
{
printf("Hello World\n");
بازگشت 0;
}

پس از پردازش توسط پیش پردازنده، متن منبع برنامه ما متورم شد و شکلی ناخوانا به دست آورد. کدی که زمانی با دست خودمان تایپ می کردیم در انتهای فایل به چند خط کاهش یافت. دلیل آن درج فایل هدر کتابخانه استاندارد است سی. فایل هدر stdio.h خود شامل موارد مختلفی است و همچنین نیاز به گنجاندن سایر فایل های هدر دارد.

به پسوند فایل hello.i توجه کنید. با توافقات gccپسوند .i مربوط به فایل هایی با کد منبع در زبان است سیبدون نیاز به پردازش پیش پردازنده چنین فایل هایی با دور زدن پیش پردازنده کامپایل می شوند:

$ gcc -o سلام سلام.i
$ls
سلام سلام.c سلام.i
$ ./سلام
سلام دنیا

پس از پیش پردازش نوبت به کامپایل می رسد. کامپایلر کد منبع برنامه را در یک زبان سطح بالا به کد در زبان اسمبلی تبدیل می کند.

معنای کلمه تالیف مبهم است. برای مثال ویکی‌پدیان‌ها، با اشاره به استانداردهای بین المللیکه کامپایل "تبدیل توسط برنامه کامپایلر است کد منبعهر برنامه ای که در یک زبان برنامه نویسی سطح بالا، به زبانی نزدیک به کد ماشین یا به کد شیء نوشته شده باشد." در اصل، این تعریف برای ما مناسب است، زبان اسمبلی واقعاً به زبان ماشین نزدیک تر است. سی. اما در زندگی روزمره، کامپایل اغلب به عنوان هر عملیاتی که کد منبع یک برنامه را در هر زبان برنامه نویسی به کد اجرایی تبدیل می کند، درک می شود. یعنی فرآیندی که شامل هر چهار مرحله فوق باشد را نیز می توان کامپایل نامید. ابهام مشابهی در متن حاضر وجود دارد. از سوی دیگر، عملیات تبدیل متن منبع یک برنامه به کد زبان اسمبلی را می توان با کلمه ترجمه نیز نشان داد - "تبدیل یک برنامه ارائه شده در یکی از زبان های برنامه نویسی به برنامه ای به زبان دیگر و به یک معنا، معادل اولی.»

توقف فرآیند ایجاد یک فایل اجرایی در پایان کامپایل اجازه می دهد گزینه -S:

$ gcc -S hello.c
$ls
hello.c سلام.s
$file hello.s
hello.s: متن برنامه اسمبلر ASCII
$ کمتر hello.s
فایل "hello.c"
بخش .rodata
LC0:
.string "Hello World"
.متن
.glob main
اصلی را تایپ کنید، @function
اصلی:
pushl %ebp
movl %esp، %ebp
andl -16 دلار، %esp
زیر 16 دلار، %esp
movl $.LC0، (%esp)
تماس قرار می دهد
movl $0، %eax
ترک کردن
ret
.سایز اصلی، .-اصلی


فایل hello.s در دایرکتوری ظاهر شد که شامل اجرای برنامه به زبان اسمبلی است. توجه داشته باشید که تعیین نام فایل خروجی با گزینه -oدر این مورد لازم نبود gccبه طور خودکار آن را با جایگزینی پسوند .c با .s در نام فایل منبع تولید می کند. برای اکثر عملیات های اساسی gccنام فایل خروجی با چنین جایگزینی تشکیل می شود. پسوند .s برای فایل های منبع زبان اسمبلی استاندارد است.

البته می توانید کد اجرایی را از فایل hello.s نیز دریافت کنید:

$ gcc -o سلام hello.s
$ls
سلام سلام.ج سلام.s
$ ./سلام
سلام دنیا

مرحله بعدی عملیات اسمبلی، ترجمه کد زبان اسمبلی به کد ماشین است. نتیجه عملیات یک فایل شی است. یک فایل شی شامل بلوک هایی از کد ماشین آماده برای اجرا، بلوک های داده و لیستی از توابع و متغیرهای خارجی تعریف شده در فایل ( جدول نمادها ، اما در عین حال، آدرس مطلق ارجاعات به توابع و داده ها را مشخص نمی کند. یک فایل شی را نمی توان مستقیما اجرا کرد، اما بعداً (در مرحله پیوند) می توان آن را با فایل های شی دیگر ترکیب کرد (در این صورت مطابق با جداول نماد، آدرس ارجاعات متقابل موجود بین فایل ها محاسبه و پر می شود. ). گزینه gcc-c، فرآیند را در پایان مرحله مونتاژ متوقف می کند:

$ gcc -c hello.c
$ls
hello.c سلام.o
$file hello.o
hello.o: ELF 64 بیتی LSB قابل جابجایی، x86-64، نسخه 1 (SYSV)، بدون حذف

فایل های شی از پسوند استاندارد .o استفاده می کنند.

اگر فایل شی دریافتی hello.o به لینک دهنده ارسال شود، لینکر آدرس لینک ها را محاسبه می کند، کد شروع و پایان برنامه، کد فراخوانی توابع کتابخانه را اضافه می کند و در نتیجه ما یک آماده خواهیم داشت. فایل اجرایی برنامه ساخته شده است.

$ gcc -o سلام hello.o
$ls
سلام سلام.c hello.o
$ ./سلام
سلام دنیا

کاری که ما اکنون انجام داده ایم (یا بهتر است بگوییم gccبرای ما انجام داد) و محتوای آخرین مرحله وجود دارد - پیوند (پیوند، پیوند).

خب، شاید در مورد تالیف و همه. حالا بیایید به برخی از گزینه های مهم، به نظر من، دست بزنیم. gcc.

گزینه -I مسیر/به/دایرکتوری/با/هدر/فایل ها - دایرکتوری مشخص شده را به لیست مسیرهای جستجو برای فایل های هدر اضافه می کند. فهرست توسط گزینه اضافه شده است -منابتدا جستجو می شود، سپس جستجو در فهرست راهنمای سیستم استاندارد ادامه می یابد. اگر گزینه ها -منچندگانه، دایرکتوری هایی که آنها مشخص می کنند با ظاهر شدن گزینه ها از چپ به راست اسکن می شوند.

گزینه دیوار- هشدارهای ناشی از خطاهای احتمالی در کد را نمایش می دهد که مانع از کامپایل برنامه نمی شود، اما به گفته کامپایلر می تواند منجر به مشکلات خاصی در هنگام اجرای آن شود. یک گزینه مهم و مفید، توسعه دهندگان gccتوصیه می شود همیشه از آن استفاده کنید. به عنوان مثال، هنگام تلاش برای کامپایل چنین فایلی، هشدارهای زیادی صادر می شود:

1 /* remark.c */
2
3 ایستا بین المللی k = 0
4 ایستا بین المللیل( بین المللیآ)؛
5
6 اصلی ()
7 {
8
9 بین المللیآ؛
10
11 بین المللیقبل از میلاد مسیح؛
12
13b + 1;
14
15b=c;
16
17 بین المللی*پ؛
18
19b = *p;
20
21 }


$ gcc -o remark remark.c
$ gcc -Wall -o remark remark.c
remark.c:7: warning: نوع پیش‌فرض بازگشت به 'int'

remark.c:13: warning: بیانیه بدون تأثیر
remark.c:9: warning: متغیر "a" استفاده نشده
remark.c:21: warning: کنترل به پایان عملکرد non-void می رسد
remark.c: در سطح بالا:
remark.c:3: warning: 'k' تعریف شده اما استفاده نشده است
remark.c:4: warning: "l" "ایستا" را اعلام کرد اما هرگز تعریف نشد
remark.c: در تابع 'main':
remark.c:15: warning: 'c' در این تابع بدون مقدار اولیه استفاده می شود
remark.c:19: warning: 'p' در این تابع بدون مقدار اولیه استفاده می شود

گزینه -خطا- همه هشدارها را به خطا تبدیل می کند. در صورت بروز اخطار، فرآیند کامپایل را متوقف می کند. همراه با استفاده می شود گزینه دیوار.

$ gcc -خطا -o remark remark.c
$ gcc -خطا -Wall -o remark remark.c
cc1: هشدارهایی که به عنوان خطا در نظر گرفته می شوند
remark.c:7: error: نوع پیش فرض را به 'int' برگردانید
remark.c: در تابع 'main':
remark.c:13: error: عبارت بدون تاثیر
remark.c:9: خطا: متغیر "a" استفاده نشده

گزینه -g- اطلاعات لازم برای کار دیباگر را در یک شی یا فایل اجرایی قرار می دهد gdb. هنگام ساخت یک پروژه به منظور رفع اشکال بعدی، گزینه -gباید هم در زمان کامپایل و هم در زمان پیوند گنجانده شود.

گزینه های -O1، -O2، -O3- سطح بهینه سازی کد تولید شده توسط کامپایلر را تنظیم کنید. با افزایش تعداد، درجه بهینه سازی افزایش می یابد. عملکرد گزینه ها را می توان در این مثال مشاهده کرد.

فایل اصلی:

/* circle.c */

اصلی( خالی )
{

بین المللیمن؛

برای(i = 0 ؛ i< 10 ; ++i)
;

برگشتمن؛

کامپایل با سطح بهینه سازی پیش فرض:

$ gcc -S circle.c
دایره های کمتر از دلار
فایل "circle.c"
.متن
.glob main
اصلی را تایپ کنید، @function
اصلی:
pushl %ebp
movl %esp، %ebp
زیر 16 دلار، %esp
movl $0, -4 (%ebp)
jmp .L2
L3:
addl $1, -4 (%ebp)
.L2:
cmpl $9, -4 (%ebp)
jle .L3
movl -4(%ebp)، %eax
ترک کردن
ret
.سایز اصلی، .-اصلی
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
.section .note.GNU-stack,""@progbits

کامپایل با حداکثر سطح بهینه سازی:

$ gcc -S -O3 circle.c
دایره های کمتر از دلار
فایل "circle.c"
.متن
.p2align 4.15
.glob main
اصلی را تایپ کنید، @function
اصلی:
pushl %ebp
movl 10 دلار، %eax
movl %esp، %ebp
popl %ebp
ret
.سایز اصلی، .-اصلی
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
.section .note.GNU-stack,""@progbits

در مورد دوم، حتی اشاره ای به هیچ چرخه ای در کد حاصل وجود ندارد. در واقع مقدار i را می توان در مرحله کامپایل محاسبه کرد که انجام شد.

افسوس، برای پروژه های واقعی، تفاوت در عملکرد در سطوح مختلف بهینه سازی تقریبا غیر قابل مشاهده است ...

گزینه -O0- هر گونه بهینه سازی کد را لغو می کند. این گزینه در مرحله اشکال زدایی برنامه مورد نیاز است. همانطور که در بالا نشان داده شد، بهینه سازی می تواند منجر به تغییر در ساختار برنامه غیرقابل تشخیص شود، ارتباط بین فایل اجرایی و کد منبع صریح نخواهد بود، به ترتیب، اشکال زدایی گام به گام برنامه امکان پذیر نخواهد بود. وقتی گزینه فعال است -g، توصیه می شود شامل و -O0.

گزینه OS- بهینه سازی را نه با کارایی کد، بلکه با اندازه فایل حاصل مشخص می کند. عملکرد برنامه باید با عملکرد کد به دست آمده از کامپایل با سطح بهینه سازی پیش فرض قابل مقایسه باشد.

گزینه -مارچ=معماری- معماری هدف پردازنده را مشخص می کند. لیست معماری های پشتیبانی شده گسترده است، به عنوان مثال، برای پردازنده های خانواده اینتل/AMDمی توانید تنظیم کنید i386, پنتیوم, پرسکات, opteron-sse3و غیره. کاربران توزیع های باینری باید در نظر داشته باشند که برای اینکه برنامه های دارای گزینه مشخص شده به درستی کار کنند، مطلوب است که تمام کتابخانه های موجود با یک گزینه کامپایل شوند.

گزینه های ارسال شده به پیوند دهنده در زیر مورد بحث قرار خواهند گرفت.

اضافه کوچک:

در بالا گفته شد gccنوع (زبان برنامه نویسی) فایل های منتقل شده را با پسوند آنها تعیین می کند و مطابق با نوع (زبان) حدس زده شده، اقداماتی را روی آنها انجام می دهد. کاربر موظف است بر روی پسوند فایل های ایجاد شده نظارت داشته باشد و آنها را طبق توافق نامه ها انتخاب کند gcc. در حقیقت gccمی توانید فایل هایی با نام دلخواه قرار دهید. گزینه gcc -xبه شما امکان می دهد زبان برنامه نویسی فایل های کامپایل شده را به صراحت مشخص کنید. عمل گزینه برای همه فایل‌های بعدی فهرست شده در فرمان اعمال می‌شود (تا ظاهر شدن گزینه بعدی -ایکس). آرگومان های گزینه ممکن:

c-header c-cpp-output

c++ c++-header c++-cpp-output

هدف-c هدف-c-هدر هدف-c-cpp-خروجی

هدف-c++ هدف-c++-هدر هدف-c++-cpp-خروجی

اسمبلر اسمبلر-با-cpp

آدا

f77 f77-cpp-input

f95 f95-cpp-input

جاوا

هدف استدلال ها باید از نوشته آنها روشن باشد (اینجا cppهیچ ربطی به C++، این یک فایل کد منبع است که توسط پیش پردازنده از قبل پردازش شده است). بیایید بررسی کنیم:

$mv hello.c hello.txt
$ gcc -Wall -x c -o hello hello.txt
$ ./سلام
سلام دنیا

تالیف جداگانه

نقطه قوت زبان ها C/C++توانایی تقسیم کد منبع برنامه به چندین فایل است. حتی می توانید بیشتر بگویید - امکان تدوین جداگانه اساس زبان است، بدون آن استفاده موثر سیقابل تصور نیست این برنامه نویسی چند فایلی است که به شما امکان می دهد روی آن پیاده سازی کنید سیپروژه های بزرگ مانند لینوکس(اینجا زیر کلمه لینوکسهم هسته و هم سیستم به عنوان یک کل دلالت دارند). چه چیزی کامپایل جداگانه به برنامه نویس می دهد؟

1. به شما امکان می دهد کد برنامه (پروژه) را خواناتر کنید. فایل منبع برای چند ده صفحه تقریباً طاقت فرسا می شود. اگر مطابق با برخی منطق (از پیش تنظیم شده)، آن را به تعدادی قطعات کوچک (هر کدام در یک فایل جداگانه) تقسیم کنید، کنار آمدن با پیچیدگی پروژه بسیار آسان تر خواهد بود.

2. زمان کامپایل مجدد پروژه را کاهش می دهد. اگر تغییراتی در یک فایل ایجاد شود، کامپایل مجدد کل پروژه بی معنی است، کافی است فقط این فایل تغییر یافته را دوباره کامپایل کنید.

3. به شما امکان می دهد کار روی پروژه را بین چندین توسعه دهنده توزیع کنید. هر برنامه نویس بخشی از پروژه خود را ایجاد و اشکال زدایی می کند، اما در هر زمان امکان جمع آوری (بازسازی) تمام پیشرفت های حاصل در محصول نهایی وجود خواهد داشت.

4. بدون تالیف جداگانه، هیچ کتابخانه ای وجود نخواهد داشت. از طریق کتابخانه ها، استفاده مجدد و توزیع کد به C/C++و کد باینری است که از یک سو به توسعه دهندگان امکان می دهد مکانیسم ساده ای را برای گنجاندن آن در برنامه های خود ارائه دهند و از سوی دیگر جزئیات پیاده سازی خاصی را از آنها پنهان کنند. آیا هنگام کار بر روی یک پروژه، آیا همیشه ارزش آن را دارد که به آن فکر کنید و در آینده به چیزی از آنچه قبلاً انجام شده است نیاز نداشته باشید؟ شاید ارزش آن را داشته باشد که بخشی از کد را از قبل به عنوان کتابخانه برجسته و مرتب کنیم؟ به نظر من، این رویکرد زندگی را تا حد زیادی ساده می کند و در زمان بسیار صرفه جویی می کند.

شورای همکاری خلیج فارسالبته از کامپایل جداگانه پشتیبانی می کند و به دستورالعمل خاصی از کاربر نیاز ندارد. به طور کلی، همه چیز بسیار ساده است.

در اینجا یک مثال عملی (هر چند بسیار بسیار مشروط) آورده شده است.

مجموعه فایل های کد منبع:

/* main.c */

#عبارتند از

#include "first.h"
#include "second.h"

بین المللیاصلی( خالی )
{

اولین()؛
دومین()؛

Printf("عملکرد اصلی... \n" );

برگشت 0 ;


/* first.h */

خالیاولین( خالی );


/* first.c */

#عبارتند از

#include "first.h"

خالیاولین( خالی )
{

Printf("اولین تابع... \n" );


/* second.h */

خالیدومین( خالی );


/* second.c */

#عبارتند از

#include "second.h"

خالیدومین( خالی )
{

Printf("تابع دوم... \n" );

به طور کلی این را داریم:

$ls
اول.ج اول.ساعت اصلی.ج دوم.ج دوم.س

تمام این اقتصاد را می توان در یک دستور کامپایل کرد:

$ gcc -Wall -o main main.c first.c second.c
$ ./اصلی
اولین تابع ...
تابع دوم ...
عملکرد اصلی ...

فقط این عملا هیچ جایزه ای به ما نمی دهد، خوب، به استثنای کدهای ساختاریافته تر و قابل خواندن، که در چندین فایل پخش شده است. تمام مزایای ذکر شده در بالا در مورد این رویکرد برای تدوین ظاهر می شود:

$ gcc -Wall -c main.c
$ gcc -Wall -c first.c
$ gcc -Wall -c second.c
$ls
اول.ج اول ساعت اول.و اصلی.ج اصلی.و دوم.ج دوم.س دوم.او
$ gcc -o main main.o first.o second.o
$ ./اصلی
اولین تابع ...
تابع دوم ...
عملکرد اصلی ...

ما چه کرده ایم؟ از هر فایل منبع (کامپایل با گزینه ) یک فایل شی دریافت کرد. سپس فایل های شی به فایل اجرایی نهایی پیوند داده شدند. البته دستور میده gccموارد بیشتری وجود دارد، اما هیچ کس پروژه ها را به صورت دستی مونتاژ نمی کند، ابزارهای اسمبلر برای این کار وجود دارد (محبوب ترین ساختن). هنگام استفاده از ابزارهای اسمبلر، تمام مزایای کامپایل جداگانه ذکر شده در بالا خود را نشان می دهد.

این سوال پیش می‌آید: چگونه پیوند دهنده می‌تواند فایل‌های شی را کنار هم قرار دهد و آدرس‌دهی تماس را به درستی محاسبه کند؟ او از کجا می داند که فایل second.o حاوی کد تابع second() و کد فایل main.o شامل فراخوانی آن است؟ به نظر می رسد که همه چیز ساده است - در فایل شی به اصطلاح وجود دارد جدول نمادها ، که شامل نام برخی از موقعیت های کد (توابع و متغیرهای خارجی) است. پیوند دهنده از طریق جدول نمادهای هر فایل شی نگاه می کند، به دنبال موقعیت های رایج (با نام های منطبق) می گردد، که بر اساس آن در مورد مکان واقعی کد توابع استفاده شده (یا بلوک های داده) نتیجه گیری می کند و بر این اساس، آدرس های تماس موجود در فایل اجرایی را دوباره محاسبه می کند.

با استفاده از ابزار می توانید جدول نمادها را مشاهده کنید نانومتر.

$nm main.o
اول شما
00000000 T اصلی
U قرار می دهد
U دوم
$nm first.o
00000000 T ابتدا
U قرار می دهد
$nm second.o
U قرار می دهد
00000000 T ثانیه

فراخوانی puts به دلیل استفاده از تابع کتابخانه استاندارد printf() است که در زمان کامپایل به puts() تبدیل شد.

جدول نمادها نه تنها در فایل شی، بلکه در فایل اجرایی نیز نوشته می شود:

$ نانومتر اصلی
08049f20d_DYNAMIC
08049ff4d _GLOBAL_OFFSET_TABLE_
080484fc R _IO_stdin_used
w _Jv_RegisterClasses
08049f10 d __CTOR_END__
08049f0cd __CTOR_LIST__
08049f18 D __DTOR_END__
08049f14 d __DTOR_LIST__
08048538r __FRAME_END__
08049f1cd __JCR_END__
08049f1c d __JCR_LIST__
0804a014 یک __bss_start
0804a00c D __data_start
080484b0 t __do_global_ctors_aux
08048360 t __do_global_dtors_aux
0804a010 D __dso_handle
w __gmon_start__
080484aa T __i686.get_pc_thunk.bx
08049f0cd __init_array_end
08049f0cd __init_array_start
08048440 T __libc_csu_fini
08048450 T __libc_csu_init
U __libc_start_main@@GLIBC_2.0
0804a014 A _edata
0804a01c A_end
080484dc T_fini
080484f8 R_fp_hw
080482b8 T _init
08048330 T_start
0804a014b تکمیل شد.7021
0804a00c W data_start
0804a018 b dtor_idx.7023
ابتدا 0804840c T
080483c0 t frame_dummy
080483e4 T اصلی
U puts@@GLIBC_2.0
08048420 T ثانیه

گنجاندن یک جدول نماد در فایل اجرایی به ویژه برای سهولت اشکال زدایی ضروری است. در اصل، برای اجرای برنامه واقعاً نیازی نیست. برای برنامه های اجرایی واقعی، با تعاریف بسیاری از تابع و متغیرهای خارجی، که شامل مجموعه ای از کتابخانه های مختلف است، جدول نمادها بسیار گسترده می شود. برای کاهش حجم فایل خروجی، می توان آن را با استفاده از حذف کرد گزینه gcc -s.

$ gcc -s -o main main.o first.o second.o
$ ./اصلی
اولین تابع ...
تابع دوم ...
عملکرد اصلی ...
$ نانومتر اصلی
nm: اصلی: بدون علامت

لازم به ذکر است که در حین پیوند، پیوند دهنده هیچ گونه بررسی را در زمینه فراخوانی تابع انجام نمی دهد، نوع مقدار بازگشتی و نوع و تعداد پارامترهای دریافتی را نظارت نمی کند (و جایی برای دریافت چنین چیزی ندارد. اطلاعات از). تمام اعتبار سنجی تماس ها باید در زمان کامپایل انجام شود. در مورد برنامه نویسی چند فایلی، لازم است از مکانیزم فایل های هدر زبان برای این کار استفاده شود. سی.

کتابخانه ها

کتابخانه - به زبان سی، یک فایل حاوی کد شی است که می تواند با استفاده از کتابخانه در مرحله پیوند به یک برنامه پیوست شود. در واقع، یک کتابخانه مجموعه ای از فایل های شی پیوند شده ویژه است.

هدف کتابخانه ها ارائه مکانیزم استاندارد برای استفاده مجدد از کد به برنامه نویس است و مکانیسم آن ساده و قابل اعتماد است.

از نظر سیستم عامل و نرم افزارهای کاربردی، کتابخانه ها هستند ایستا و به اشتراک گذاشته شده است (پویا ).

کد کتابخانه ایستا در فایل اجرایی در هنگام پیوند دومی گنجانده شده است. کتابخانه به فایل "هاردسیم" می شود، کد کتابخانه با بقیه کد فایل "ادغام" می شود. برنامه ای که از کتابخانه های ایستا استفاده می کند، مستقل می شود و می تواند تقریباً روی هر رایانه ای با معماری و سیستم عامل مناسب اجرا شود.

کد کتابخانه اشتراکی به درخواست برنامه در حین اجرای آن توسط سیستم عامل بارگذاری شده و به کد برنامه مرتبط می شود. کد کتابخانه پویا در فایل اجرایی برنامه گنجانده نشده است، تنها پیوندی به کتابخانه در فایل اجرایی موجود است. در نتیجه، برنامه ای که از کتابخانه های مشترک استفاده می کند، دیگر مستقل نیست و تنها می تواند با موفقیت در سیستمی اجرا شود که کتابخانه های مربوطه در آن نصب شده اند.

پارادایم کتابخانه مشترک سه مزیت قابل توجه دارد:

1. حجم فایل اجرایی بسیار کاهش یافته است. در سیستمی که شامل تعداد زیادی باینری است که از یک کد استفاده می کنند، نیازی به نگهداری یک کپی از آن کد برای هر فایل اجرایی نیست.

2. کد کتابخانه اشتراکی که توسط چندین برنامه استفاده می شود در یک نمونه در RAM ذخیره می شود (در واقع، به این سادگی نیست...) که منجر به کاهش نیاز سیستم به RAM موجود می شود.

3. اگر تغییراتی در کد کتابخانه مشترک ایجاد شود، نیازی به بازسازی هر فایل اجرایی نیست. تغییرات و اصلاحات در کد کتابخانه پویا به طور خودکار در هر یک از برنامه هایی که از آن استفاده می کنند منعکس می شود.

بدون پارادایم کتابخانه مشترک، هیچ توزیع از پیش کامپایل شده (دودویی) وجود نخواهد داشت لینوکس(بله، مهم نیست). اندازه توزیعی را تصور کنید که دارای کد کتابخانه استاندارد در هر دودویی باشد. سی(و سایر کتابخانه های موجود). فقط تصور کنید که برای به روز رسانی سیستم، پس از رفع یک آسیب پذیری مهم در یکی از کتابخانه های پرکاربرد، چه کاری باید انجام دهید...

حالا برای کمی تمرین

برای نشان دادن، اجازه دهید از مجموعه فایل های منبع مثال قبلی استفاده کنیم. بیایید کد (اجرای) توابع اول () و دوم () را در کتابخانه خانگی خود قرار دهیم.

لینوکس طرح نام‌گذاری زیر را برای فایل‌های کتابخانه دارد (اگرچه همیشه رعایت نمی‌شود) - نام فایل کتابخانه با پیشوند lib شروع می‌شود و پس از آن نام واقعی کتابخانه و در پایان با پسوند .a (. بایگانی ) - برای کتابخانه استاتیک، .so ( شی مشترک ) - برای یک مشترک (پویا)، پس از گسترش، ارقام شماره نسخه از طریق یک نقطه (فقط برای یک کتابخانه پویا) فهرست می شوند. نام فایل هدر مربوط به کتابخانه (دوباره، به عنوان یک قاعده) از نام کتابخانه (بدون پیشوند و نسخه) و پسوند .h تشکیل شده است. به عنوان مثال: libogg.a، libogg.so.0.7.0، ogg.h.

ابتدا بیایید یک کتابخانه استاتیک ایجاد و استفاده کنیم.

توابع اول () و دوم () محتویات کتابخانه libhello ما را تشکیل می دهند. نام فایل کتابخانه به ترتیب libhello.a خواهد بود. بیایید فایل هدر hello.h را با کتابخانه مقایسه کنیم.

/* سلام */

خالیاولین( خالی );
خالیدومین( خالی );

البته خطوط:

#include "first.h"


#include "second.h"

در فایل‌های main.c، first.c و second.c باید با:

#شامل "hello.h"

خوب، اکنون، دنباله دستورات زیر را وارد کنید:

$ gcc -Wall -c first.c
$ gcc -Wall -c second.c
$ ar crs libhello.a اول.o دوم.o
$filelibhello.a
libhello.a: آرشیو فعلی

همانطور که قبلا ذکر شد، یک کتابخانه مجموعه ای از فایل های شی است. با دو دستور اول این فایل های شی را ایجاد کردیم.

در مرحله بعد، باید فایل های شی را به یک مجموعه پیوند دهید. برای این کار از بایگانی کننده استفاده می شود. ar- ابزار "چسب" چندین فایل را به یک، در آرشیو حاصل شامل اطلاعات مورد نیاز برای بازیابی (استخراج) هر فایل جداگانه (از جمله ویژگی های مالکیت، دسترسی، زمان) آن است. هرگونه "فشرده سازی" محتویات بایگانی یا تغییر شکل دیگر داده های ذخیره شده انجام نمی شود.

c گزینه arname- یک آرشیو ایجاد کنید، اگر بایگانی با نام arname وجود نداشته باشد، ایجاد می شود، در غیر این صورت فایل ها به آرشیو موجود اضافه می شوند.

گزینه r- حالت به روز رسانی بایگانی را تنظیم می کند، اگر فایلی با نام مشخص شده از قبل در بایگانی وجود داشته باشد، حذف می شود و فایل جدید به انتهای آرشیو اضافه می شود.

گزینه ها- فهرست آرشیو (به روز رسانی) را اضافه می کند. در این حالت، فهرست آرشیو جدولی است که در آن برای هر نام نمادین (نام تابع یا بلوک داده) تعریف شده در فایل های آرشیو شده، نام فایل شی مربوطه با آن مرتبط می شود. نمایه آرشیو برای سرعت بخشیدن به کار با کتابخانه ضروری است - برای یافتن تعریف مورد نظر، نیازی به بررسی جداول نماد همه پرونده های آرشیو نیست، می توانید بلافاصله به فایل حاوی نام مورد نظر بروید. . می توانید فهرست بایگانی را با استفاده از ابزار از قبل آشنا مشاهده کنید نانومتراستفاده از آن گزینه ها(جدول نماد تمام فایل های شی آرشیو نیز نشان داده می شود):

$ nm -s libhello.a
فهرست آرشیو:
اول در first.o
دوم در second.o

first.o:
00000000 T ابتدا
U قرار می دهد

second.o:
U قرار می دهد
00000000 T ثانیه

برای ایجاد یک فهرست آرشیو، یک ابزار ویژه وجود دارد ranlib. کتابخانه libhello.a می توانست به این صورت ایجاد شود:

$ ar cr libhello.a اول.o دوم.o
$ranlib libhello.a

با این حال، کتابخانه بدون فهرست آرشیو به خوبی کار خواهد کرد.

حالا بیایید از کتابخانه خود استفاده کنیم:

$ gcc -Wall -c main.c
$
$ ./اصلی
اولین تابع ...
تابع دوم ...
عملکرد اصلی ...

آثار...

خب حالا نظرات ... دو تا گزینه جدید هست gcc:

گزینه نام -l- ارسال به لینک کننده، نشان دهنده نیاز به پیوند کتابخانه libname به فایل اجرایی است. اتصال به معنای نشان دادن این است که فلان توابع (متغیرهای خارجی) در فلان کتابخانه تعریف شده است. در مثال ما، کتابخانه ثابت است، همه نام‌های نمادین به کدی که مستقیماً در فایل اجرایی قرار دارد اشاره می‌کنند. به گزینه ها توجه کنید -lنام کتابخانه به عنوان نام بدون پیشوند lib داده می شود.

گزینه -L /path/to/directory/with/libraries - به پیوند دهنده ارسال می شود، مسیر دایرکتوری حاوی کتابخانه های پیوند شده را مشخص می کند. در مورد ما، نکته . ، پیوند دهنده ابتدا به دنبال کتابخانه ها در دایرکتوری فعلی، سپس در دایرکتوری های تعریف شده در سیستم می گردد.

در اینجا لازم است یک نکته کوچک بیان شود. واقعیت این است که برای تعدادی از گزینه ها gccترتیب ظاهر شدن آنها در خط فرمان مهم است. اینگونه است که پیوند دهنده به دنبال کدی می گردد که با نام های مشخص شده در جدول نماد فایل در کتابخانه های فهرست شده در خط فرمان مطابقت داشته باشد. بعد ازنام این فایل محتویات کتابخانه های فهرست شده قبل از نام فایل توسط پیوند دهنده نادیده گرفته می شود:

$ gcc -Wall -c main.c
$ gcc -o اصلی -L. -سلام main.o
main.o: در تابع «main»:
main.c:(.text+0xa): ارجاع تعریف نشده به "first"
main.c:(.text+0xf): ارجاع تعریف نشده به «ثانیه»

$ gcc -o main main.o -L. -سلام
$ ./اصلی
اولین تابع ...
تابع دوم ...
عملکرد اصلی ...

این نوع رفتار gccبا توجه به تمایل توسعه دهندگان به ارائه قابلیت ترکیب فایل ها با کتابخانه ها به روش های مختلف در اختیار کاربر، استفاده از نام های متقاطع ... به نظر من در صورت امکان بهتر است این کار را به خود زحمت ندهید. به طور کلی، کتابخانه های پیوند باید بعد از نام فایلی که به آنها ارجاع می دهد، فهرست شوند.

وجود دارد راه جایگزینتعیین مکان کتابخانه ها در سیستم. بسته به توزیع، متغیر محیطی LD_LIBRARY_PATH یا LIBRARY_PATH می‌تواند فهرستی از دایرکتوری‌های جدا شده با کولون را در خود نگه دارد که در آن پیوند دهنده باید به دنبال کتابخانه‌ها باشد. به عنوان یک قاعده، به طور پیش فرض، این متغیر اصلا تعریف نشده است، اما هیچ چیز مانع از ایجاد آن نمی شود:

$ اکو $LD_LIBRARY_PATH

/usr/lib/gcc/i686-pc-linux-gnu/4.4.3/../../../../i686-pc-linux-gnu/bin/ld: نمی توان -lhello را پیدا کرد
collect2: ld با کد برگشتی 1 خارج شد
$ صادرات LIBRARY_PATH=.
$ gcc -o main.o -lhello
$ ./اصلی
اولین تابع ...
تابع دوم ...
عملکرد اصلی ...

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

حال بیایید کتابخانه پویا را ایجاد و استفاده کنیم.

مجموعه فایل های منبع بدون تغییر باقی می ماند. ما دستورات را وارد می کنیم، ببینید چه اتفاقی افتاده است، نظرات را بخوانید:

$ gcc -Wall -fPIC -c first.c
$ gcc -Wall -fPIC -c second.c
$ gcc -shared -o libhello.so.2.4.0.5 -Wl,-soname,libhello.so.2 first.o second.o

در نتیجه چه چیزی به دست آوردی؟

$ فایل libhello.so.2.4.0.5
libhello.so.2.4.0.5: ELF 64 بیتی LSB شی اشتراکی، x86-64، نسخه 1 (SYSV)، پیوند پویا، بدون حذف

فایل libhello.so.2.4.0.5 کتابخانه مشترک ما است. بیایید در مورد نحوه استفاده از آن در زیر صحبت کنیم.

حالا نظرات:

گزینه -fPIC- نیاز به کامپایلر، هنگام ایجاد فایل های شی، برای تولید دارد کد مستقل موقعیت (PIC - کد مستقل موقعیت ، تفاوت اصلی آن در نحوه ارائه آدرس ها است. به جای تعیین موقعیت های ثابت (استاتیک)، همه آدرس ها بر اساس افست های مشخص شده در جدول افست جهانی (جدول افست جهانی - GOT ). فرمت کد مستقل از موقعیت به شما امکان می دهد ماژول های اجرایی را به کد برنامه اصلی در زمان بارگذاری آن متصل کنید. بر این اساس، هدف اصلی کدهای مستقل از موقعیت، ایجاد کتابخانه‌های پویا (اشتراک‌گذاری شده) است.

-گزینه اشتراکی- نشان می دهد gcc، که در نتیجه، نه یک فایل اجرایی باید ساخته شود، بلکه یک شی مشترک - یک کتابخانه پویا.

گزینه -Wl,-soname,libhello.so.2- مجموعه ها سونام کتابخانه ها در پاراگراف بعدی به تفصیل در مورد سونام صحبت خواهیم کرد. حالا بیایید در مورد قالب گزینه بحث کنیم. این ساخت عجیب و غریب در نگاه اول با کاما برای تعامل مستقیم بین کاربر و پیوند دهنده در نظر گرفته شده است. در طول تدوین gccپیوند دهنده را به طور خودکار، به طور خودکار، بنا به صلاحدید خود فرا می خواند، gccگزینه های لازم برای تکمیل موفقیت آمیز کار را به آن منتقل می کند. در صورتی که کاربر نیاز به دخالت خود در فرآیند لینک دهی داشته باشد، می تواند از گزینه ویژه استفاده کند gcc -Wl، -option، value1، value2 .... انتقال به پیوند دهنده به چه معناست ( -Wl) گزینه -گزینهبا استدلال ارزش 1, ارزش 2و غیره در مورد ما، این گزینه به پیوند دهنده داده شد -سونامبا استدلال libello.so.2.

حالا در مورد سونام هنگام ایجاد و توزیع کتابخانه ها، مشکل سازگاری و کنترل نسخه وجود دارد. برای اینکه سیستم، به ویژه بارگذار کتابخانه پویا، ایده ای داشته باشد که از کدام نسخه کتابخانه هنگام کامپایل برنامه استفاده شده است و بر این اساس، برای عملکرد موفقیت آمیز آن ضروری است، یک شناسه ویژه ارائه شده است - سونام ، هم در خود فایل کتابخانه و هم در فایل اجرایی برنامه قرار می گیرد. شناسه soname رشته ای است حاوی نام کتابخانه با پیشوند lib، یک نقطه، پسوند so، یک نقطه دوباره، و یک یا دو رقم (جدا شده با نقطه) از نسخه کتابخانه، نام lib .so. ایکس . y یعنی soname با نام فایل کتابخانه تا رقم اول یا دوم شماره نسخه مطابقت دارد. اجازه دهید نام فایل اجرایی کتابخانه ما libhello.so.2.4.0.5 باشد، سپس نام soname کتابخانه می تواند libhello.so.2 باشد. هنگام تغییر رابط کتابخانه، سونام آن باید تغییر کند! هر تغییر کدی که باعث ناسازگاری با نسخه‌های قبلی شود باید با نام جدید همراه باشد.

چطور کار میکند؟ اجازه دهید برای اجرای موفقیت آمیز برخی از برنامه ها کتابخانه ای با نام hello لازم باشد، بگذارید یکی در سیستم وجود داشته باشد و نام فایل کتابخانه libhello.so.2.4.0.5 باشد و نام کتابخانه که در آن نوشته شده است libhello است. .پس.2. در مرحله کامپایل اپلیکیشن، لینک دهنده طبق گزینه -سلام، در سیستم فایلی به نام libhello.so را جستجو می کند. در یک سیستم واقعی، libhello.so یک پیوند نمادین به فایل libhello.so.2.4.0.5 است. پس از دسترسی به فایل کتابخانه، پیوند دهنده مقدار soname ثبت شده در آن را می خواند و از جمله آن را در فایل اجرایی برنامه قرار می دهد. هنگامی که برنامه راه اندازی می شود، بارگذار کتابخانه پویا درخواستی برای گنجاندن کتابخانه ای با نام سونام خوانده شده از فایل اجرایی دریافت می کند و سعی می کند کتابخانه ای را در سیستم پیدا کند که نام فایل آن با نام سونام مطابقت داشته باشد. یعنی لودر سعی می کند فایل libhello.so.2 را پیدا کند. اگر سیستم به درستی پیکربندی شده باشد، باید حاوی یک پیوند نمادین libhello.so.2 به فایل libhello.so.2.4.0.5 باشد، لودر به کتابخانه مورد نیاز دسترسی پیدا می کند و سپس بدون تردید (و بدون بررسی چیز دیگری) آن را به برنامه متصل کنید. حال تصور کنید که ما برنامه کامپایل شده را به سیستم دیگری منتقل کرده ایم که تنها نسخه قبلی کتابخانه با soname libhello.so.1 مستقر است. تلاش برای اجرای برنامه منجر به خطا می شود، زیرا فایلی به نام libhello.so.2 در این سیستم وجود ندارد.

بنابراین در زمان کامپایل، پیوند دهنده باید یک فایل کتابخانه (یا یک پیوند نمادین به یک فایل کتابخانه) به نام lib name ارائه دهد. بنابراین، در زمان اجرا، لودر به یک فایل (یا یک پیوند نمادین) به نام lib name .so نیاز دارد. ایکس . y نام lib name .so چه ربطی به آن دارد. ایکس . y باید با رشته soname کتابخانه استفاده شده مطابقت داشته باشد.

در توزیع های باینری، به عنوان یک قاعده، فایل کتابخانه libhello.so.2.4.0.5 و پیوند به آن libhello.so.2 در بسته libhello قرار می گیرد و پیوند libhello.so که فقط برای کامپایل ضروری است، همراه با فایل هدر کتابخانه hello.h در بسته libhello-devel بسته بندی می شود (بسته توسعه همچنین حاوی فایل نسخه ایستا کتابخانه libhello.a است، کتابخانه ایستا را می توان فقط در کامپایل استفاده کرد. صحنه). هنگام باز کردن بسته، همه فایل‌ها و پیوندهای فهرست شده (به جز hello.h) در یک فهرست قرار می‌گیرند.

بیایید مطمئن شویم که نام رشته داده شده واقعاً در فایل کتابخانه ما ثبت شده است. بیایید از ابزار مگا استفاده کنیم ابجدمپبا گزینه :

$ objdump -p libhello.so.2.4.0.5 | grep SONAME
libello.so.2


سودمند ابجدمپ- یک ابزار قدرتمند که به شما امکان می دهد اطلاعات جامعی در مورد محتوای داخلی (و ساختار) یک شی یا فایل اجرایی به دست آورید. صفحه مرد ابزار این را می گوید ابجدمپاول از همه، برای برنامه نویسانی مفید خواهد بود که ابزارهای اشکال زدایی و کامپایل ایجاد می کنند، و نه فقط نوشتن برخی برنامه های کاربردی :) به ویژه، با گزینه این یک جداکننده است ما از گزینه استفاده کرده ایم - نمایش متا اطلاعات مختلف در مورد فایل شی.

در مثال بالا از ایجاد یک کتابخانه، ما بی وقفه از اصول تدوین جداگانه پیروی کردیم. البته با یک تماس می توان کتابخانه را به این صورت کامپایل کرد gcc:

$ gcc -shared -Wall -fPIC -o libhello.so.2.4.0.5 -Wl,-soname,libhello.so.2 first.c second.c

حالا بیایید سعی کنیم از کتابخانه به دست آمده استفاده کنیم:

$ gcc -Wall -c main.c
$
/usr/bin/ld: نمی تواند -lhello را پیدا کند
collect2: ld 1 وضعیت خروج را برگرداند

پیوند دهنده قسم می خورد. آنچه در بالا در مورد پیوندهای نمادین گفته شد را به خاطر بسپارید. libhello.so را ایجاد کنید و دوباره امتحان کنید:

$ ln -s libhello.so.2.4.0.5 libhello.so
$ gcc -o main main.o -L. -lhello -Wl,-rpath,.

الان همه خوشحالن باینری تولید شده را اجرا کنید:

خطا... لودر شکایت می کند، نمی تواند کتابخانه libhello.so.2 را پیدا کند. بیایید مطمئن شویم که پیوند به libhello.so.2 واقعاً در فایل اجرایی ثبت شده است:

$ objdump -p main | grep مورد نیاز است
libello.so.2
libc.so.6

$ ln -s libhello.so.2.4.0.5 libhello.so.2
$ ./اصلی
اولین تابع ...
تابع دوم ...
عملکرد اصلی ...

کار کرد... حالا در مورد گزینه های جدید نظر می دهد gcc.

گزینه -Wl,-rpath,.- ساخت و ساز از قبل آشنا، یک گزینه را به پیوند دهنده منتقل کنید -rpathبا استدلال . . با استفاده از -rpathدر فایل اجرایی برنامه، می‌توانید مسیرهای دیگری را اضافه کنید که در آن بارگذار کتابخانه مشترک، فایل‌های کتابخانه را جستجو می‌کند. در مورد ما، مسیر . - جستجو برای فایل های کتابخانه از دایرکتوری فعلی شروع می شود.

$ objdump -p main | grep RPATH
RPATH .

به لطف این گزینه، هنگام شروع برنامه، نیازی به تغییر متغیرهای محیطی نیست. واضح است که اگر برنامه را به دایرکتوری دیگری منتقل کنید و سعی کنید آن را اجرا کنید، فایل کتابخانه پیدا نمی شود و لودر پیغام خطا را نشان می دهد:

اصلی $mv..
$ ../اصلی
اولین تابع ...
تابع دوم ...
عملکرد اصلی ...

همچنین می‌توانید با استفاده از این ابزار دریابید که یک برنامه به کدام کتابخانه‌های مشترک نیاز دارد ldd:

$ ldd اصلی
linux-vdso.so.1 => (0x00007fffaddff000)
libhello.so.2 => ./libhello.so.2 (0x00007f9689001000)
libc.so.6 => /lib/libc.so.6 (0x00007f9688c62000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9689205000)

در نتیجه lddبرای هر کتابخانه مورد نیاز، سونام آن و مسیر کامل فایل کتابخانه، مطابق با تنظیمات سیستم تعیین شده است.

اکنون زمان آن است که در مورد اینکه فایل های کتابخانه قرار است در کجا در سیستم قرار گیرند، جایی که لودر تلاش می کند آنها را پیدا کند و نحوه مدیریت این فرآیند صحبت کنیم.

طبق توافقات FHS (استاندارد سلسله مراتب سیستم فایل)سیستم باید دو (حداقل) دایرکتوری برای ذخیره فایل های کتابخانه داشته باشد:

/lib - در اینجا کتابخانه های اصلی کیت توزیع، لازم برای کار برنامه ها از /bin و /sbin جمع آوری شده است.

/usr/lib - کتابخانه های مورد نیاز برنامه های کاربردی از /usr/bin و /usr/sbin در اینجا ذخیره می شوند.

فایل های هدر مربوط به کتابخانه ها باید در پوشه /usr/include باشد.

لودر پیش فرض فایل های کتابخانه ای را در این دایرکتوری ها جستجو می کند.

علاوه بر موارد ذکر شده در بالا، دایرکتوری /usr/local/lib باید در سیستم وجود داشته باشد - باید کتابخانه هایی وجود داشته باشند که توسط کاربر به تنهایی مستقر شده و سیستم مدیریت بسته را دور بزنند (در کیت توزیع موجود نیست). به عنوان مثال، کتابخانه های کامپایل شده از منابع به طور پیش فرض در این دایرکتوری خواهند بود (برنامه های نصب شده از منابع در /usr/local/bin و /usr/local/sbin قرار می گیرند، البته ما در مورد توزیع های باینری صحبت می کنیم). فایل‌های هدر کتابخانه‌ها در این مورد در /usr/local/include قرار می‌گیرند.

در برخی از توزیع ها (در اوبونتو) لودر برای مشاهده دایرکتوری /usr/local/lib پیکربندی نشده است، بنابراین اگر کاربر کتابخانه را از منبع نصب کند، سیستم آن را نخواهد دید. این توزیع توسط نویسندگان توزیع به طور خاص برای آموزش نصب به کاربر ساخته شده است نرم افزارفقط از طریق سیستم مدیریت بسته. در ادامه نحوه اقدام در این مورد توضیح داده خواهد شد.

در واقع، به منظور ساده سازی و سرعت بخشیدن به فرآیند یافتن فایل های کتابخانه، بارگذار در هر بار دسترسی به دایرکتوری های بالا نگاه نمی کند، بلکه از پایگاه داده ذخیره شده در فایل /etc/ld.so.cache (library) استفاده می کند. کش). این شامل اطلاعاتی در مورد جایی است که فایل کتابخانه مربوط به نام داده شده در سیستم قرار دارد. لودر با دریافت لیستی از کتابخانه های مورد نیاز یک برنامه خاص (لیستی از کتابخانه های soname مشخص شده در فایل اجرایی برنامه)، مسیر فایل هر کتابخانه مورد نیاز را با استفاده از /etc/ld.so.cache تعیین می کند و آن را بارگیری می کند. به حافظه علاوه بر این، لودر می‌تواند دایرکتوری‌های فهرست شده در متغیرهای سیستم LD_LIBRARY_PATH، LIBRARY_PATH و در قسمت RPATH فایل اجرایی (به بالا مراجعه کنید) را مرور کند.

این ابزار برای مدیریت و به روز نگه داشتن حافظه پنهان کتابخانه استفاده می شود. ldconfig. اگر اجرا شود ldconfigبدون هیچ گزینه ای، برنامه به دایرکتوری های مشخص شده در خط فرمان، دایرکتوری های قابل اعتماد /lib و /usr/lib، دایرکتوری های فهرست شده در فایل /etc/ld.so.conf نگاه می کند. برای هر فایل کتابخانه ای که در دایرکتوری های مشخص شده یافت می شود، یک soname خوانده می شود، یک پیوند نمادین بر اساس soname ایجاد می شود و اطلاعات در /etc/ld.so.cache به روز می شود.

بیایید مطمئن شویم که:

$ls
hello.h libhello.so libhello.so.2.4.0.5 main.c
$
$ sudo ldconfig /full/path/to/dir/c/example
$ls
hello.h libhello.so libhello.so.2 libhello.so.2.4.0.5 main main.c
$ ./اصلی
اولین تابع ...
تابع دوم ...
عملکرد اصلی ...

اولین تماس ldconfigما کتابخانه خود را در حافظه پنهان ذخیره کردیم، آن را با تماس دوم حذف کردیم. توجه داشته باشید که هنگام کامپایل گزینه اصلی حذف شده است -Wl،-rpath،.، در نتیجه، لودر کتابخانه های مورد نیاز را فقط در حافظه پنهان جستجو کرد.

اکنون باید مشخص شود که اگر پس از نصب کتابخانه از منبع، سیستم آن را مشاهده نکرد، چه باید کرد. اول از همه، باید مسیر کامل دایرکتوری را با فایل های کتابخانه (به طور پیش فرض /usr/local/lib) به فایل /etc/ld.so.conf اضافه کنید. فرمت /etc/ld.so.conf - فایل حاوی فهرستی از دایرکتوری های جدا شده با کولون، فاصله، برگه یا خط جدید است که در آن می توان کتابخانه ها را جستجو کرد. سپس تماس بگیرید ldconfigبدون هیچ گزینه، اما با حقوق ابرکاربر. همه چیز باید کار کند.

خوب، در پایان، بیایید در مورد اینکه چگونه نسخه های ایستا و پویا کتابخانه ها با هم کنار می آیند صحبت کنیم. سوال واقعی چیست؟ در بالا، هنگام بحث در مورد نام های پذیرفته شده و مکان فایل های کتابخانه، گفته شد که فایل های نسخه استاتیک و پویا کتابخانه در یک دایرکتوری ذخیره می شوند. چگونه gccبفهمیم که از چه نوع کتابخانه ای می خواهیم استفاده کنیم؟ به طور پیش فرض، کتابخانه پویا ترجیح داده می شود. اگر پیوند دهنده یک فایل کتابخانه پویا پیدا کند، در پیوند دادن آن به فایل اجرایی برنامه تردیدی ندارد:

$ls
hello.h libhello.a libhello.so libhello.so.2 libhello.so.2.4.0.5 main.c
$ gcc -Wall -c main.c
$ gcc -o main main.o -L. -lhello -Wl,-rpath,.
$ ldd اصلی
linux-vdso.so.1 => (0x00007fffe1bb0000)
libhello.so.2 => ./libhello.so.2 (0x00007fd50370b000)
libc.so.6 => /lib/libc.so.6 (0x00007fd50336c000)
/lib64/ld-linux-x86-64.so.2 (0x00007fd50390f000)
$ du -h اصلی
12K اصلی

به حجم فایل اجرایی برنامه دقت کنید. حداقل ممکن است. تمام کتابخانه های مورد استفاده به صورت پویا پیوند داده شده اند.

وجود دارد گزینه gcc -static- دستورالعملی به پیوند دهنده برای استفاده از نسخه های ثابت تمام کتابخانه های لازم برای برنامه:

$ gcc -static -o main.o -L. -سلام
فایل اصلی $
اصلی: ELF 64 بیتی LSB قابل اجرا، x86-64، نسخه 1 (GNU/Linux)، پیوند استاتیک، برای گنو/لینوکس 2.6.15، بدون حذف
$ ldd اصلی
یک فایل اجرایی پویا نیست
$ du -h اصلی
اصلی 728K

اندازه فایل اجرایی 60 برابر بزرگتر از مثال قبلی است - کتابخانه های زبان استاندارد در فایل گنجانده شده است. سی. اکنون برنامه ما می تواند با خیال راحت از دایرکتوری به دایرکتوری و حتی به ماشین های دیگر منتقل شود، کد hello library داخل فایل است، برنامه کاملاً مستقل است.

اگر لازم باشد فقط بخشی از کتابخانه های مورد استفاده به صورت ایستا پیوند داده شود؟ نوع احتمالیراه حل این است که نام نسخه استاتیک کتابخانه را با نام مشترک متفاوت کنید و هنگام کامپایل برنامه مشخص کنید که این بار از کدام نسخه استفاده کنیم:

$ mv libhello.a libhello_s.a
$ gcc -o main main.o -L. -سلام_ها
$ ldd اصلی
linux-vdso.so.1 => (0x00007fff021f5000)
libc.so.6 => /lib/libc.so.6 (0x00007fd0d0803000)
/lib64/ld-linux-x86-64.so.2 (0x00007fd0d0ba4000)
$ du -h اصلی
12K اصلی

از آنجایی که اندازه کد کتابخانه libhello ناچیز است،

$ du -h libhello_s.a
4.0K libhello.a

اندازه فایل اجرایی حاصل عملاً با اندازه فایل ایجاد شده با استفاده از پیوند پویا برابر است.

خب، شاید همین باشد. با تشکر فراوان از همه کسانی که خواندن را در این مرحله به پایان رساندند.

عموماً اعتقاد بر این است که GCC از نظر عملکرد از سایر کامپایلرها عقب است. در این مقاله سعی می کنیم بفهمیم که چه بهینه سازی های اساسی کامپایلر GCC باید برای دستیابی به عملکرد قابل قبول اعمال شود.

گزینه های پیش فرض در GCC چیست؟

(1) به طور پیش فرض، GCC از سطح بهینه سازی "-O0" استفاده می کند. واضح است که از نظر کارایی مطلوب نیست و برای تدوین محصول نهایی توصیه نمی شود.
GCC تا زمانی که گزینه "-march=native" ارسال نشود، معماری را که کامپایل روی آن اجرا می شود، تشخیص نمی دهد. به طور پیش فرض، GCC از گزینه تنظیم شده در طول پیکربندی خود استفاده می کند. برای اطلاع از پیکربندی GCC، کافی است:

این بدان معنی است که GCC "-march=corei7" را به گزینه های شما اضافه می کند (مگر اینکه معماری دیگری مشخص شده باشد).
اکثر کامپایلرهای GCC برای x86 (پایه برای لینوکس 64 بیتی) به گزینه های داده شده اضافه می کنند: "-mtune=generic -march=x86-64"، زیرا هیچ گزینه خاص معماری در پیکربندی داده نشده است. همیشه می توانید با دستور زیر تمام گزینه های تصویب شده هنگام راه اندازی GCC و همچنین گزینه های داخلی آن را پیدا کنید:

در نتیجه معمولاً استفاده می شود:

تعیین معماری مورد استفاده برای عملکرد مهم است.تنها استثنا را می توان برنامه هایی در نظر گرفت که فراخوانی توابع کتابخانه تقریباً کل زمان راه اندازی را می گیرد. GLIBC می تواند تابع بهینه را برای یک معماری معین در زمان اجرا انتخاب کند. توجه به این نکته مهم است که وقتی به صورت ایستا پیوند داده می شوند، برخی از توابع GLIBC برای معماری های مختلف نسخه نمی شوند. یعنی اگر سرعت توابع GLIBC مهم باشد مونتاژ پویا بهتر است..
(2) به طور پیش فرض، اکثر کامپایلرهای GCC برای x86 در حالت 32 بیتی از مدل ممیز شناور x87 استفاده می کنند، زیرا آنها بدون "-mfpmath=sse" پیکربندی شده اند. فقط در صورتی که پیکربندی GCC حاوی "--with-mfpmath=sse" باشد:

کامپایلر به صورت پیش‌فرض از مدل SSE استفاده می‌کند، در سایر موارد، بهتر است گزینه -mfpmath=sse را در حالت 32 بیتی به بیلد اضافه کنید.
بنابراین، معمولا استفاده می شود:

افزودن گزینه ”-mfpmath=sse” در حالت 32 بیتی مهم است! استثنا کامپایلری است که "--with-mfpmath=sse" را در پیکربندی خود دارد.

حالت 32 بیتی یا 64 بیتی؟

حالت 32 بیتی معمولاً برای کاهش میزان استفاده از حافظه و در نتیجه سرعت بخشیدن به کار با آن (داده های بیشتری در حافظه پنهان قرار می گیرد) استفاده می شود.
در حالت 64 بیتی (در مقایسه با 32 بیتی) تعداد رجیسترهای عمومی موجود از 6 به 14 افزایش می یابد، ثبت XMM از 8 به 16. همچنین، تمام معماری های 64 بیتی از پسوند SSE2 پشتیبانی می کنند، بنابراین در حالت 64 بیتی نیازی به گزینه "-mfpmath" =sse را اضافه کنید.
توصیه می شود از حالت 64 بیتی برای کارهای محاسباتی و حالت 32 بیتی برای برنامه های موبایل استفاده کنید.

چگونه حداکثر کارایی را بدست آوریم؟

مجموعه ای از گزینه ها برای به حداکثر رساندن عملکرد وجود ندارد، اما گزینه های زیادی در GCC وجود دارد که ارزش امتحان کردن را دارند. در زیر جدولی با گزینه‌های پیشنهادی و پیش‌بینی رشد پردازنده‌های Intel Atom و نسل دوم اینتل Core i7 نسبت به گزینه «-O2» آورده شده است. پیش‌بینی‌ها بر اساس میانگین هندسی نتایج مجموعه خاصی از وظایف است که توسط GCC نسخه 4.7 گردآوری شده است. همچنین فرض می‌کند که پیکربندی کامپایلر برای x86-64 عمومی انجام شده است.
پیش بینی افزایش عملکرد برای برنامه های موبایلنسبت به "-O2" (فقط در حالت 32 بیتی، زیرا برای بخش تلفن همراه اصلی است):

پیش بینی افزایش عملکرد در وظایف محاسباتی نسبت به "-O2" (در حالت 64 بیتی):
-m64 -Ofast -flto ~17%
-m64 -Ofast -flto -march=بومی ~21%
-m64 -Ofast -flto -march=native -funroll-loops ~22%

مزیت حالت 64 بیتی نسبت به 32 بیت برای کارهای محاسباتی با گزینه های "-O2 -mfpmath=sse" حدود 5٪ است.
تمام داده های مقاله پیش بینی بر اساس نتایج مجموعه معینی از معیارها است.
در زیر توضیحاتی در مورد گزینه های استفاده شده در مقاله آورده شده است. توضیحات کامل (به زبان انگلیسی): http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/Optimize-Options.html"
  • "-Ofast" مانند "-O3 -fast-math" سطح بالاتری از بهینه‌سازی و بهینه‌سازی‌های تهاجمی‌تر را برای محاسبات حسابی (مانند ارتباط مجدد واقعی) ممکن می‌سازد.
  • بهینه سازی های متقابل ماژول "-flto".
  • حالت 32 بیتی "-m32".
  • "-mfpmath=sse" رجیسترهای XMM را قادر می سازد در محاسبات واقعی (به جای پشته واقعی در حالت x87) استفاده شوند.
  • "-funroll-loops" باز کردن حلقه را فعال می کند

لجستیک حمل و نقل(تحلیل انواع مختلف حمل و نقل: مزایا، معایب)

حمل و نقل شاخه ای از تولید مواد است که افراد و کالاها را حمل می کند. در ساختار تولید اجتماعیحمل و نقل متعلق به حوزه تولید خدمات مادی است.

خاطرنشان می شود که بخش قابل توجهی از عملیات لجستیکی در مسیر جریان مواد از منبع اولیه مواد اولیه تا مصرف نهایی با استفاده از انواع مختلف انجام می شود. وسیله نقلیه. هزینه این عملیات تا 50 درصد کل هزینه لجستیک است.

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

حمل و نقل غیر عمومی - حمل و نقل درون تولیدی، و همچنین وسایل نقلیه از هر نوع متعلق به سازمان های غیر حمل و نقل.

سازمان جابجایی کالا با حمل و نقل غیر عمومی موضوع مطالعه لجستیک صنعتی است. مشکل انتخاب کانال های توزیع در حوزه لجستیک توزیع حل شده است.

بنابراین، انواع اصلی حمل و نقل زیر وجود دارد:

راه آهن

رودخانه آب داخلی

خودرو

هوا

خط لوله

هر یک از روش های حمل و نقل دارای ویژگی های خاصی از نظر مدیریت لجستیک، مزایا و معایب است که امکان استفاده از آن را در سیستم لجستیک تعیین می کند. انواع مختلف حمل و نقل مجموعه حمل و نقل را تشکیل می دهند. مجموعه حمل و نقل روسیه توسط اشخاص حقوقی و افراد ثبت شده در قلمرو آن تشکیل شده است - کارآفرینانی که فعالیت های حمل و نقل و حمل و نقل را در انواع حمل و نقل، طراحی، ساخت، تعمیر و نگهداری راه آهن، جاده ها و سازه های روی آنها، خطوط لوله، کار انجام می دهند. مربوط به نگهداری سازه های هیدرولیکی قابل کشتیرانی، آب و راه های هواییپیام ها، برگزاری تحقیق علمیو آموزش پرسنل، شرکت هایی که بخشی از سیستم حمل و نقل هستند و وسایل نقلیه تولید می کنند و همچنین سازمان هایی که سایر کارهای مربوط به فرآیند حمل و نقل را انجام می دهند. TC روسیه بیش از 160 هزار کیلومتر راه آهن اصلی و جاده دسترسی، 750 هزار کیلومتر جاده آسفالته، 1.0 میلیون کیلومتر خطوط کشتیرانی دریایی، 101 هزار کیلومتر داخلی است. آبراه ها، 800 هزار کیلومتر خطوط هوایی. روزانه حدود 4.7 میلیون تن بار از طریق این ارتباطات تنها با وسایل حمل و نقل عمومی جابجا می شود (بر اساس داده های سال 2000)، بیش از 4 میلیون نفر در TC کار می کنند و سهم حمل و نقل از تولید ناخالص داخلی کشور حدود 9 درصد است. بنابراین حمل و نقل جزء ضروری زیرساخت های اقتصاد و کل پتانسیل اجتماعی و تولیدی کشور ما است.

روی میز. 1 (4، 295) ویژگی های لجستیکی مقایسه ای انواع مختلف حمل و نقل داده شده است.

جدول 1 ویژگی های شیوه های حمل و نقل

نوع حمل و نقل

مزایای

ایرادات

راه آهن

ظرفیت حمل بالا و توان عملیاتی. استقلال از شرایط آب و هوایی، زمان سال و روز.

نظم بالای حمل و نقل نرخ های نسبتا پایین؛ تخفیف قابل توجه برای محموله های ترانزیت. سرعت بالاتحویل کالا در فواصل طولانی

تعداد محدودی از اپراتورها سرمایه گذاری کلان در تولید و پایه فنی. مصرف مواد بالا و شدت انرژی حمل و نقل. دسترسی کم به نقاط پایانی فروش (مصرف).

ایمنی ناکافی محموله

امکان حمل و نقل بین قاره ای. هزینه کم حمل و نقل در مسافت های طولانی. ظرفیت حمل و جابجایی بالا. شدت سرمایه کم حمل و نقل.

حمل و نقل محدود

سرعت تحویل کم (زمان حمل و نقل طولانی).

وابستگی به شرایط جغرافیایی، ناوبری و آب و هوا.

لزوم ایجاد زیرساخت بندری پیچیده.

آب داخلی (رودخانه)

ظرفیت حمل بالا در رودخانه ها و مخازن اعماق دریا.

هزینه کم حمل و نقل. شدت سرمایه پایین

حمل و نقل محدود سرعت تحویل کم

وابستگی به اعماق ناهموار رودخانه ها و مخازن، شرایط ناوبری. فصلی بودن قابلیت اطمینان ناکافی حمل و نقل و ایمنی محموله.

خودرو

در دسترس بودن بالا.

امکان تحویل بار درب منزل

مانور بالا، انعطاف پذیری، پویایی. سرعت تحویل بالا. امکان استفاده از مسیرهای مختلف و طرح های تحویل.

امنیت بالای بار. امکان ارسال بار در دسته های کوچک.

کارآیی پایین. وابستگی به شرایط آب و هوایی و جاده. هزینه حمل و نقل نسبتاً بالا در مسافت های طولانی.

پاکیزگی ناکافی محیط

هوا

بالاترین سرعت تحویل بار. قابلیت اطمینان بالا.

بالاترین ایمنی بار.

کوتاه ترین مسیرهای حمل و نقل

هزینه حمل و نقل بالا، بالاترین نرخ در بین سایر روش های حمل و نقل. شدت سرمایه بالا، شدت مواد و انرژی حمل و نقل. وابسته به آب و هوا دسترسی جغرافیایی ناکافی

خط لوله

کم هزینه. کارایی بالا (پهنای باند). امنیت بالای بار. شدت سرمایه پایین

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

بنابراین، اول از همه، مدیر لجستیک باید تصمیم بگیرد که آیا ناوگان وسایل نقلیه خود را ایجاد کند یا از حمل و نقل اجاره ای (عمومی یا خصوصی) استفاده کند. هنگام انتخاب یک جایگزین، آنها معمولاً از سیستم خاصی از معیارها استفاده می کنند که عبارتند از: هزینه ایجاد و راه اندازی ناوگان وسایل نقلیه شخصی شما. هزینه پرداخت خدمات حمل و نقل، شرکت های حمل و نقل و سایر واسطه های لجستیکی در حمل و نقل سرعت حمل و نقل

کیفیت حمل و نقل (قابلیت اطمینان تحویل، ایمنی بار و غیره)

در بیشتر موارد، شرکت های تولیدی به خدمات شرکت های حمل و نقل تخصصی متوسل می شوند.