Блоґ одного кібера

Історія хвороби контуженого інформаційним вибухом

Лінкер для чайників

with 4 comments

50 хвилинна лекція через jabber

pidginЗазвичай хороші лекції виходять при вузькій цільовій аудиторії. Якщо ця аудиторія – одна людина – то лекція стає майже ідеальна. Цікаво перевірити, чи ще хтось крім зможе розібратись з принципами компіляції багатомодульних програм на C++ за допомогою пояснення Улідтки? Тоді його витрачена година буде витрачена з більшою користю. Хоча текст концентрований. Якщо справитесь менш ніж за 5 хв, розкажіть, як враження.

(17:59:55) bunyk: А я от не знаю що прочитати, щоб зрозуміти що таке лінкер.
(18:00:31) Улідтко: дивись
(18:00:38) Улідтко: у тебе є main.c
(18:00:52) Улідтко: який робить #include "foo.h"
(18:01:11) Улідтко: відповідно, є окремі foo.c і foo.h
(18:01:56) Улідтко: в foo.h визначені деякі інтерфейсні типи даних та функція func, що працює з ними
(18:02:29) Улідтко: (як ми знаємо, в .h заголовках функції лише декларуються, коду там немає)
(18:02:45) Улідтко: власне код функції описується в foo.c
(18:02:58) bunyk: ну…
(18:03:11) Улідтко: Подумаєм, як проходить процес компіляції
(18:04:17) Улідтко: gcc foo.c -o foo.o дасть нам "об’єктний файл" foo.o
(18:04:32) Улідтко: що таке об’єктний файл?
(18:04:49) Улідтко: це файл, в якому зберігається машинний код функції func
(18:05:11) Улідтко: все, лише самі інструкції
(18:05:21) Улідтко: платформно-залежні і все таке
(18:05:56) Улідтко: для його генерації компілятор використовував метадані із foo.h і код із foo.c
(18:06:22) Улідтко: далі — як компілюється main.c?
(18:06:40) bunyk: gcc main.c main -lfoo
(18:06:43) bunyk: ?
(18:06:50) Улідтко: %)
(18:06:51) Улідтко: ні
(18:07:16) Улідтко: отримаєш якийсь різновид file not found
(18:07:45) Улідтко: зрозуміло, що gcc main.c -o main не піде
(18:07:47) Улідтко: чому?
(18:08:11) bunyk: бо нема коду func
(18:08:38) Улідтко: правильно, хоча власне компілятору це не заважає
(18:09:00) Улідтко: він знає, які у func аргументи і тип результату
(18:09:10) Улідтко: (із foo.h)
(18:09:49) Улідтко: і вільно перетворює виклики func у машинний код
(18:10:51) Улідтко: він може це робити, бо у .o файлах конкретні адреси (функцій, змінних) не використовуються
(18:11:10) Улідтко: використовуються символічні імена, references
(18:11:41) Улідтко: таким чином ми можемо скомпілювати_ main.c в main.o
(18:12:12) Улідтко: але воно не є придатним для виконання
(18:12:23) Улідтко: бо нема конкретних адрес, які потрібні процесору
(18:12:35) Улідтко: от в цьому і полягає задача лінкера
(18:13:43) Улідтко: він повинен сумістити main.o і foo.o в один виконуваний файл, склавши все це в купу і назначивши усім символічним іменам конкретні адреси
(18:14:20) Улідтко: і позамінявши усі звернення до цих символічних імен на їх конкретні адреси
(18:15:28) Улідтко: тобто, в коді функції main виклики call func будуть перетворені в call 0x04053a8, наприклад
(18:15:37) Улідтко: (в машинному коді)
(18:16:04) Улідтко: це все уже буде придатним до виконання на процесорі
(18:16:41) Улідтко: Прикол в тому, що gcc насправді для полегшення нашого життя викликає лінкера самотужки
(18:17:11) Улідтко: (а також препроцесора, асемблера та інші штуки)
(18:17:35) Улідтко: від чого, власне, і вийде помилка при спробі gcc main.c -o main
(18:17:47) Улідтко: воно скаже undefined reference
(18:18:18) Улідтко: бо ніякої конкретної адреси для func не знайдеться
(18:18:43) Улідтко: От це все називається статичним лінкуванням.
(18:19:13) Улідтко: весь код включається в результатуючий виконуваний файл
(18:20:31) bunyk: так, як таки його відкомпілювати
(18:20:49) Улідтко: gcc main.c foo.c -o main 🙂
(18:21:08) bunyk: ага. 🙂
(18:21:11) Улідтко: це найзручніший і найнеочевидніший спосіб, коротше 🙂
(18:21:38) Улідтко: якщо змусити gcc бути лише компілятором, то сценарій був би приблизно таким:
(18:21:50) Улідтко: gcc foo.c -o foo.o
(18:21:56) Улідтко: gcc main.c -o main.o
(18:22:16) Улідтко: ld foo.o main.o
(18:22:37) Улідтко: ну, і перейменувати готовий a.out
(18:23:40) bunyk: а -lGL що значить? Шукати об’єктний код десь в /usr/lib ?
(18:23:59) Улідтко: так, чекай
(18:24:05) Улідтко: це лише одна модель лінкування
(18:24:08) Улідтко: статичне
(18:24:45) Улідтко: Припустимо далі, шо ти використовуєш якусь велику класну бібліотеку, типу OpenGL
(18:25:31) Улідтко: якшо ти будеш лінкувати статично
(18:25:44) Улідтко: то весь код цієї бібліотеки потрабить до твого бінарника
(18:26:14) Улідтко: далі, хтось інший пише іншу прожку, яка теж використовує OpenGL
(18:26:32) Улідтко: або ти пишеш другу прожку, яка теж використовує OpenGL
(18:26:56) Улідтко: і, якщо лінкувати далі статично, то код бібліотеки потрапляє і туди, і туди, і туди.
(18:27:09) Улідтко: це викликає тучу проблем
(18:27:20) Улідтко: очевидно, що бінарний код бібліотеки дублюється
(18:27:24) bunyk: а це щось типу dll?
(18:27:34) Улідтко: витрачаючи різні види ресурсів
(18:28:40) Улідтко: … і ускладнюючи підтримку софта (обновлення бібліотеки не будуть використовуватися прожками, поки вони не будуть перекомпільовані)
(18:29:04) Улідтко: от для цього і є динамічне лінкування
(18:29:23) Улідтко: так, dll — це dynamic linking library
(18:31:09) Улідтко: воно заключається в тому, що твоя прожка має в бінарному вигляді має так звану таблицю імпорту
(18:31:33) Улідтко: це куча змінних
(18:31:38) Улідтко: вказівників
(18:31:55) Улідтко: а саме, вказівників на функції
(18:32:18) Улідтко: на функції з тих самих dll’ок
(18:33:44) Улідтко: при запуску такої прожки операційна система знаходить і завантажує усі потрібні dll’ки, а потім заносить у цю саму таблицю імпорту всі вказівники на реальні функції з бібліотек
(18:35:33) Улідтко: знову ж таки, власне компілятора не хвилює де саме будуть розміщені коди функцій
(18:35:50) Улідтко: хоча, насправді, ні, хвилює 🙂
(18:36:16) Улідтко: синтаксично функції позначаються як імпортовані із dll
(18:36:49) Улідтко: а компілятор замість call func генерує інструкціє вигляду call [_import_table_entry_for_func]
(18:37:33) Улідтко: тобто, адреса для call витягується із пам’яті за адресою _import_table_entry_for_func
(18:38:00) Улідтко: _import_table_entry_for_func — це, ефективно, змінна-вказівник
(18:38:35) Улідтко: знову ж таки, компілятор не знає (і не має знати) точної, конкретної адреси цього вказівника
(18:38:57) Улідтко: він звертається до нього символічно
(18:39:32) Улідтко: а задача лінкера в цьому випадку — окрім іншого, сформувати таблицю імпорту
(18:40:59) Улідтко: розмістити, як і раніше, змінні-вказівники, і на додаток до цього, надати інформацію, потрібну для операційної системи для заватнаження dll’ок
(18:41:50) Улідтко: *.dll під юніксами мають імена lib*.so
(18:42:15) bunyk: я знав! /usr/lib/ 🙂
(18:42:22) Улідтко: угу
(18:43:11) Улідтко: але в /usr/lib є два набори файлів: lib*.so та lib*.a
(18:44:18) Улідтко: .a — це просто архіви .o, тобто це static link library
(18:45:49) Улідтко: max@ulidtko:~$ ls /usr/lib/libGL.so
(18:46:19) Улідтко: відповідно, щоб скомпілювати прожку із використанням OpenGL, ти маєш сказати
(18:46:30) Улідтко: gcc main.c -lGL
(18:47:26) Улідтко: щоб лінкер, викликаний gcc, прикрутив усі gl* функції до динамічної бібліотеки libGL.so
(18:47:38) bunyk: Ооо! Тепер ясно.
(18:48:31) bunyk: Дякую.

Advertisements

Written by bunyk

Вересень 14, 2009 at 15:31

Оприлюднено в Інструменти, Кодерство

Tagged with ,

Відповідей: 4

Subscribe to comments with RSS.

  1. улідтко рулєз! реально у людини є задатки вчителя, що вміє нормально (тобто достатньо легко, проте без сюсюкання) пояснити тему. Мій респект!

    verdakafo

    Вересень 15, 2009 at 00:50

  2. 🙂 цікаво-цікаво

    P.S.: Підозрюючи, що повідомлення на пошту може Вам про коментар так і ненадійти, напишу сюди, що побажання буде враховане 🙂 З радістю напишу що-небудь цікавеньке про C/C++ в своєму блозі найближчим часом 🙂

    GrAndSE

    Жовтень 18, 2009 at 19:17

  3. Відразу видно що людина має чималий досвід C++, а якщо він ще може добре і зрозуміло пояснити, то йому ціни немає 🙂

    maaxws

    Жовтень 26, 2009 at 22:42

  4. Спасибо, с пользой потратил 5 минут 🙂

    jtimv

    Січень 7, 2010 at 19:37


Залишити відповідь

Заповніть поля нижче або авторизуйтесь клікнувши по іконці

Лого WordPress.com

Ви коментуєте, використовуючи свій обліковий запис WordPress.com. Log Out / Змінити )

Twitter picture

Ви коментуєте, використовуючи свій обліковий запис Twitter. Log Out / Змінити )

Facebook photo

Ви коментуєте, використовуючи свій обліковий запис Facebook. Log Out / Змінити )

Google+ photo

Ви коментуєте, використовуючи свій обліковий запис Google+. Log Out / Змінити )

З’єднання з %s

%d блогерам подобається це: