Це, мабуть, більш детальна відповідь, ніж ви хотіли, але я вважаю, що гідне пояснення виправдане.
У C і C ++ один вихідний файл визначається як одна одиниця перекладу . За умовою, файли заголовків містять декларації функцій, визначення типів та визначення класів. Фактичні реалізації функцій перебувають у блоках перекладу, тобто .cpp-файлах.
Ідея цього полягає в тому, що функції та функції членів класу / структури збираються та збираються один раз, тоді інші функції можуть викликати цей код з одного місця без створення дублікатів. Ваші функції неявно оголошуються як "зовнішні".
/* Function declaration, usually found in headers. */
/* Implicitly 'extern', i.e the symbol is visible everywhere, not just locally.*/
int add(int, int);
/* function body, or function definition. */
int add(int a, int b)
{
return a + b;
}
Якщо ви хочете, щоб функція була локальною для одиниці перекладу, ви визначаєте її як "статичну". Що це означає? Це означає, що якщо ви включаєте вихідні файли з функціями зовнішньої форми, ви отримаєте помилки переосмислення, оскільки компілятор не раз стикається з тією ж реалізацією. Отже, ви хочете, щоб усі ваші перекладацькі одиниці бачили декларацію функції, але не тіло функції .
То як все це пюре в кінці? Це робота лінкера. Лінкер зчитує всі об'єктні файли, що генеруються на етапі асемблера, і розв'язує символи. Як я вже говорив раніше, символ - це лише назва. Наприклад, ім'я змінної або функції. Коли підрозділи перекладу, які викликають функції або оголошують типи, не знають реалізації для цих функцій чи типів, ці символи вважаються невирішеними. Лінкер розв'язує невирішений символ, з'єднуючи блок перекладу, який містить не визначений символ разом з тим, який містить реалізацію. Phew. Це справедливо для всіх зовнішньо видимих символів, незалежно від того, чи вони реалізовані у вашому коді, чи надані додатковою бібліотекою. Бібліотека - це справді архів із кодом для багаторазового використання.
Є два помітні винятки. По-перше, якщо у вас є невелика функція, ви можете зробити її вбудованою. Це означає, що згенерований машинний код не генерує виклик зовнішньої функції, а є буквально об'єднаним на місці. Оскільки вони зазвичай невеликі, розмір накладних витрат не має значення. Ви можете уявити, що вони статичні в тому, як вони працюють. Тому безпечно реалізовувати вбудовані функції у заголовках. Реалізації функцій усередині визначення класу або структури також часто підкреслюються автоматично компілятором.
Інший виняток - шаблони. Оскільки компілятору потрібно бачити ціле визначення шаблону при імментируванні, неможливо від'єднати реалізацію від визначення, як при автономних функціях або нормальних класах. Ну, можливо, це можливо і зараз, але отримання широкої підтримки компілятора для ключового слова "експорт" зайняло багато часу. Тож без підтримки "експорту" перекладацькі блоки отримують власні локальні копії створених шаблонів типів та функцій, подібно до того, як працюють вбудовані функції. З підтримкою "експорту" це не так.
За двома винятками, деякі люди вважають, що "приємніше" розміщувати реалізовані вбудовані функції, шаблонні функції та шаблонні типи у файлах .cpp, а потім #include .cpp-файл. Будь це заголовок чи вихідний файл, насправді не має значення; препроцесор не дбає і є лише умовою.
Швидкий підсумок всього процесу від коду C ++ (декілька файлів) та остаточного виконуваного файлу:
- Препроцесор запускається, який аналізує всі директиви , яка починається з "#". Директива #include, наприклад, об'єднує включений файл із нижчим. Це також робить макрозаміни та вставки лексеми.
- Фактичний компілятор працює на проміжному текстовому файлі після етапу препроцесора та видає код асемблера.
- В асемблері працює на файл збірки і висилає машинний код, зазвичай це називається об'єктний файл і слід двійковий виконуваний формат оперативної системи в питанні. Наприклад, Windows використовує PE (портативний виконуваний формат), тоді як Linux використовує формат ELF Unix System V з розширеннями GNU. На цьому етапі символи все ще позначаються як невизначені.
- Нарешті, запускається лінкер . Усі попередні етапи виконувались на кожному блоці перекладу в порядку. Однак етап linker працює на всіх згенерованих файлах об'єктів, які були створені асемблером. Лінкер розв'язує символи і робить багато магії, як створення розділів і сегментів, що залежить від цільової платформи та бінарного формату. Програмістам не потрібно цього знати загалом, але це, безумовно, допомагає в деяких випадках.
Знову ж таки, це було напевно більше, ніж ви просили, але я сподіваюся, що деталі, що містять зернистість, допоможуть вам побачити більшу картину.