Для чого потрібні header файли в с чому не можна писати без них stack overflow російською

Весь гугл перерив, не можу зрозуміти. І википедию перечитав і взагалі все що завгодно перечитав. Правда не розумію.

Що заважає підключати просто .cpp файли?

Ну підключив ти його два рази, ну нехай компілятор тільки один раз підключає і все, раз він один чорт все в один як би файл все склеює, значить перше підключення буде вище за інших і буде видно їм внизу. Нехай дивиться час зміни і перекомпілюються тільки змінилося. Нехай автоматично генерує і прикріплює header файли з описом інтерфейсів до відкомпільоване бінарники. і т.п.

Іншими словами чому рутинна робота по генерації хедер файлів, які не автоматизированности і покладання на розробника? Адже компілятор поруч з відкомпільоване бінарники може легко сам згенерувати файл опису інтерфейсів (який він отримав скануючи cpp файл).

Можете навести ситуацію в якій би виникали ПРОБЛЕМИ без використання хедер файлів? Це буде найкраще пояснення.

заданий 30 січ в 5:03

Проблема лежить в області забезпечення сумісності.

Подивіться, будь-який новий мову програмування - так навіть Паскаль, не кажучи вже про Java або C # - не потребують заголовних файлах. Без сумніву, C ++ теж міг би обійтися без них. У чому ж справа?

Перенесемося на півстоліття назад, в 1972 рік. Уявімо собі компілятор мови C.

Припустимо, ми хочемо написати дизайн компілятора. Ми не можемо скомпілювати всю програму за раз, у нас на це просто не вистачить пам'яті. Комп'ютери тоді були маленькими і повільними. Ми хочемо компілювати програму по шматочках, по кілька функцій зараз.

У нас відразу ж виникає проблема: як скомпілювати функцію f. яка посилається на іншу функцію g. Нам потрібно окреме опис інших функцій. Ми могли б, звичайно, прочитати всі вихідні файли, для початку з'ясувати, які функції у нас є, і потім прочитати їх вдруге і скомпілювати один за одним. Але це було занадто складно і повільно, потрібно було парсити визначення функцій двічі, і один раз викидати результат! Це неприпустимий витрата процесорного часу! Плюс якщо тримати в пам'яті визначення всіх функцій, може знову-таки не вистачити пам'яті.

На кого Денніс вирішив покласти складну проблему відділення опису функції від її реалізації, і підключення тільки потрібних описів при компіляції даної функції? На нас, програмістів. Він вирішив, що ми повинні самі допомогти компілятору і скопіпастіть визначення функцій в окремий файл, і самі вказати компілятору, які файли з визначеннями потрібні. (Тобто перший крок компіляції покладається на нас.)

Це радикально спростило компілятор, але привело в свою чергу до проблем. Що буде, якщо ми забули підключити потрібний заголовки? Відповідь: помилка компіляції. Що буде, якщо зміст тексту заголовки змінюється в залежності від якого-небудь макросу? Відповідь: компілятор «тупий», і не намагається детектувати цю проблему, він перекладає відповідальність на нас.

На момент розробки мови це було правильне рішення. Компілятор виявився практичним, швидким, і програмісти були не проти допомогти компіляції. Ну і якщо хто припускався помилки, сам був винен.

Перемотати стрілки годинника в 1983 рік. Бьярн створює C ++. Він вирішив злетіти на хвилі популярності мови C, і перейняв модель компіляції C з окремими translation unit'амі і пов'язаними з цим проблемами прямо з C. Втім, перші версії C ++ були просто препроцесоором мови C! Тому проблеми роздільної компіляції перекочували з C в C ++. Гірше того, додалися нові проблеми. Наприклад, шаблони класів виглядають як класи, але не генерують об'єктного коду самі по собі, тому для них доводиться йти на хитрощі і обходити недоліки системи роздільного компіляції (наприклад, включенням реалізації в header і трюками компоновщика).

Втім, існує проект модульної системи в C ++, який повинен допомогти програмістам позбутися спадщини півстолітньої давності. Він ще не реалізований, і в ньому є складнощі рівня дизайну (наприклад, в header'е був визначений макрос, чи буде він видно, якщо ми перейдемо від header'ов до модулів?) Сподіваюся, в майбутньому розробники мови таки зможуть побороти проблему зворотної сумісності.

1) ми вказуємо компілятору набір cpp файлів 2) в одних підключаємо інші за допомогою #include "file.cpp" 3) якщо підключення файлу дублюється він його не підключає вдруге 4) якщо ми компілюємо окремо, то разом з откомпіірованним бінарники повинен АВТОМАТИЧНО створюватися хедер файл містить видерти ОПИС ІНТЕРФЕЙСІВ з cpp файлу з кодом. 5) чому творці компілятора так не зробили? чому змушують руками хедери генерувати? - Maxmaxmaximus 30 Січня о 5:45

Іншими словами чому рутинна робота по генерації хедер файлів, які не автоматизированности і покладання на розробника? Адже компілятор поруч з відкомпільоване бінарники може легко сам згенерувати файл опису інтерфейсів (який він отримав скануючи cpp файл) - Maxmaxmaximus 30 січ в 5:48

Ще раз прочитайте про одиницю трансляції. Коли компілюється cpp файл, компілятор не знає нічого про інших cpp файлах (чи був він уже откомпилирован). З приводу "рутинної роботи по генерації хедер файлів". Зазвичай при написанні програм спочатку думають про інтерфейс (пишуть .h), а потім тільки пишуть реалізацію (.cpp) - Krepver 30 Січня о 5:55

Що заважає підключати просто .cpp файли?

Відсутність в них інформації про типи, доступною ззовні.

Компілятор перетворює файли вихідних кодів (.c і .cpp) в об'єктні файли (.obj. Зшиваються пізніше компоновщиком в єдиний .exe або .dll файл) - «напівфабрикати» - «чорні ящики», які імпортують і експортують символи.

Символ, в свою чергу, - це сукупність «ім'я функції / змінної - її зсув щодо початку об'єктного файлу». Імпорт - це залежності об'єктного файлу (при цьому неважливо, де вони будуть реалізовані, потрібно лише збіг імен); експорт - це те, що в цьому файлі реалізовано.

Таким чином, об'єктні файли не містять типів даних і інших абстракцій, що існують виключно в уяві компілятора. Тільки машинний код і дані, що існують у вигляді реально виділяється місця в ОЗУ, і іменовані мітки.

На жаль, одного тільки знання імені недостатньо для формування імпорту (залежно). Компілятору необхідно знати ще структуру беруть участь типів для генерації коректних зсувів на стеку, вставки додаткових викликів конструкторів / деструкторов, виконання неявних перетворень типів, перевірки коректності виклику і т. Д.

Чому ж опис типів не вбудовується в який реалізує їх об'єктний файл? Один і той же тип даних може використовуватися в довільній кількості місць, а тому подібне вбудовування порушує правило одного визначення. Згідно з цим правилом будь-яка сутність повинна мати тільки одне джерело (об'єктний файл), щоб у компоновщика не було невизначеності при зіставленні імпорту та експорту.

Ну нехай компілятор тільки один раз підключає і все, раз він один чорт все в один як би файл все склеює, значить перше підключення буде вище за інших і буде видно їм внизу.

Збірка програми на C і C ++ відбувається в два етапи.

Компілятор перетворює кожен файл вихідних кодів (.c і .cpp) в об'єктний файл (.obj). При цьому кожен файл вихідних кодів компілюється незалежно, ніби він один у всесвіті і нікого крім нього більше не існує. На цьому етапі компілятор не бачить інших об'єктних файлів. але він уже зобов'язаний видати готовий машинний код. Такий підхід був обраний розробниками мови для можливості розпаралелювання компіляції.

Компоновщик бере зазначені об'єктні файли (неважливо, як і коли вони були отримані), пов'язує їх обсяги імпорту та експорти і об'єднує в єдиний виконуваний (.exe або .dll) файл.

Іншими словами чому рутинна робота по генерації хедер файлів, які не автоматизированности і покладання на розробника? Адже компілятор поруч з відкомпільоване бінарники може легко сам згенерувати файл опису інтерфейсів (який він отримав скануючи cpp файл).

Ось вам .cpp файл (без включення нестандартних заголовків, як хотіли):

Як компілятор виведе з цього структуру класу Logger. А саме: які поля повинні бути в класі і який їхній модифікатор доступу? Адже реалізація одного класу може бути розподілена по декількох файлах, рівно як і один файл може містити реалізацію частини методів декількох класів.

Ви, напевно, скажете: «описати class Logger; тут же, щоб з нього генерувався .hpp ». А в чому тоді різниця, якщо ми це опис і так виносимо в заголовки?

Можете навести ситуацію в якій би виникали ПРОБЛЕМИ без використання хедер файлів?

ну і так далі для кожного файлу, що використовує Foo.

А тепер уявіть, що кожна зміна опису класу зажадає масової заміни в великій кількості місць. Забудете виправити - компілятор промовчить, будучи введений в оману, на підсумкова програма буде падати.

Ну підключив ти його два рази, ну нехай компілятор тільки один раз підключає і все

А якщо я не хочу один раз?

Ось припустимо, я генеруючи за рахунок макросів пачку схожих функцій, де якісь мінімальні відмінності в поведінці визначаються іншими макросами. Т. о. замість генерації коду я міняю значення констант і підключаю один і той же cpp-файл в інший cpp-файл. А ти хочеш мене цієї можливості позбавити?

відповідь дан 30 Січня о 10:13

А ти хочеш мене цієї можливості позбавити? - Ні я не хочу тебе цієї возморжності позбавляти, після проходу препроцесора компілятор дивиться змінився файл чи ні, якщо змінився, то робить вклейку вдруге. - Maxmaxmaximus 30 Січня о 12:50

@Maxmaxmaximus, цікавий варіант. Але мені він здається досить витратним - дерево залежностей і глибина вкладеності може бути дуже великий - вийде невиправдана багаторазова перевірка одних і тих же файлів. Мабуть. - Qwertiy 30 Січня о 14:20

@Qwertiy вийде невиправдана багаторазова перевірка одних і тих же файлів. - Ні, так щас інші мови роблять і це наносекундной операція. Просто лежить мап файл, которй згенерований при першій повної компіляції, в якому описані всі версії файлів і їх відповідність один одному і все таке інше. І все) компілятор не доведеться фізично пробігає по всіх файлів і сканувати, він просто завантажить МЕП файл в оперативну пам'ять і від туди всю інфу дізнається. Ну тобто такі речі як раз легко легко кешуються, і саме так влаштовані компілятори інших мов. - Maxmaxmaximus 31 січ в 0:54

@Maxmaxmaximus, в наведеному у відповіді коді один і той же файл підключається тричі (власне, один раз просто сам файл і двічі #include __FILE__). Він ні разу не змінюється, але все 3 рази компіляція призводить до різного результату. При чому тут взагалі час останньої зміни? - Qwertiy 31 Січня о 9:07