7.5: Лінкер
- Page ID
- 34514
Часто у великій програмі ви розділите код на кілька файлів, щоб зберегти пов'язані функції разом. Кожен з цих файлів можна компілювати в об'єктний код: але ваша кінцева мета - створити єдиний виконуваний файл! Там повинен бути якийсь спосіб об'єднання кожного з цих об'єктних файлів в один виконуваний файл. Ми називаємо це зв'язуванням.
Зверніть увагу, що навіть якщо ваша програма вміщається в один файл, вона все одно повинна бути пов'язана з певними системними бібліотеками, щоб працювати правильно. Наприклад, виклик printf зберігається в бібліотеці, яка повинна поєднуватися з виконуваним файлом для роботи. Таким чином, хоча вам явно не доведеться турбуватися про зв'язування в цьому випадку, є, безумовно, все ще процес зв'язування відбувається для створення вашого виконуваного файлу.
У наступних розділах ми пояснюємо деякі терміни, необхідні для розуміння посилань.
Змінні та функції мають імена у вихідному коді, на які ми посилаємося на них. Один із способів мислення заяву оголошення змінної Int a є те, що ви говорите компілятору «відкласти деяку пам'ять sizeof (int) і відтепер, коли я використовую a він буде посилатися на цю виділену пам'ять. Аналогічно функція говорить «зберігати цей код в пам'яті, і коли я викликаю function () перейти до і виконати цей код».
У цьому випадку ми викликаємо символи a і функції, оскільки вони є символічним зображенням області пам'яті.
Символи допомагають людям зрозуміти програмування. Можна сказати, що основною роботою процесу компіляції є видалення символів - процесор не знає, що представляє собою, все, що він знає, це те, що він має деякі дані за певною адресою пам'яті. Процес компіляції повинен перетворити += 2 в щось на зразок «збільшити значення в пам'яті при 0xABCDE на 2.
У деяких програмах C ви, можливо, бачили терміни static і extern, що використовуються зі змінними. Ці модифікатори можуть впливати на те, що ми називаємо видимістю символів.
Уявіть, що ви розділили вашу програму на два файли, але деякі функції повинні поділитися змінною. Ви хочете лише одне визначення (тобто розташування пам'яті) спільної змінної (інакше вона не буде спільною!) , але обидва файли повинні посилатися на нього.
Щоб включити це, ми оголошуємо змінну в одному файлі, а потім в іншому файлі оголошуємо змінну з тим же ім'ям, але з префіксом extern. extern розшифровується як зовнішній і для людини означає, що ця змінна оголошена десь в іншому місці.
Те, що extern говорить компілятору, це те, що він не повинен виділяти місця в пам'яті для цієї змінної, і залишити цей символ в об'єктному коді, де він буде виправлений пізніше. Компілятор не може знати, де насправді визначено символ, але компілятор робить, оскільки його робота полягає в тому, щоб дивитися всі об'єктні файли разом і об'єднати їх в один виконуваний файл. Таким чином, компонувальник побачить символ, що залишився у другому файлі, і сказати: «Я бачив цей символ раніше в файлі 1, і я знаю, що він відноситься до місця пам'яті 0x12345». Таким чином, він може змінити значення символу, щоб бути значенням пам'яті змінної в першому файлі.
статичний майже протилежний зовнішньому. Він встановлює обмеження на видимість символу, який він змінює. Якщо ви оголошуєте змінну зі статикою, яка говорить компілятору «не залишайте жодних символів для цього в об'єктному коді». Це означає, що коли компонувальник зв'язує разом об'єктні файли, він ніколи не побачить цей символ (і тому не може зробити, що «Я бачив це раніше!» підключення). static добре підходить для поділу та зменшення конфліктів - оголосивши змінну статичну, ви можете повторно використовувати ім'я змінної в інших файлах і не закінчитися зіткненнями символів. Ми говоримо, що ми обмежуємо видимість символу, тому що ми не дозволяємо компонувальнику бачити його. Зіткніть це з більш видимим символом (не оголошеним статичним), який можна побачити компонувальником.
Таким чином, процес зв'язування дійсно два кроки; об'єднання всіх об'єктних файлів в один виконуваний файл, а потім проходження кожного об'єктного файлу для вирішення будь-яких символів. Це, як правило, вимагає двох проходів; один, щоб прочитати всі визначення символів і взяти до уваги невирішені символи, а другий, щоб виправити всі ці невирішені символи в потрібному місці.
Остаточний виконуваний файл повинен закінчитися без невирішених символів; компонувальник зазнає невдачі з помилкою, якщо такі є. [22]
[22] Ми називаємо це статичне посилання. Динамічне зв'язування - це аналогічна концепція, що виконується під час виконання, і описана трохи пізніше.