У C ви не можете мати визначення / реалізацію функції всередині файлу заголовка. Однак у C ++ ви можете мати повну реалізацію методу всередині файлу заголовка. Чому поведінка відрізняється?
У C ви не можете мати визначення / реалізацію функції всередині файлу заголовка. Однак у C ++ ви можете мати повну реалізацію методу всередині файлу заголовка. Чому поведінка відрізняється?
Відповіді:
У випадку C, якщо ви визначаєте функцію у файлі заголовка, ця функція з’явиться у кожному складеному модулі, що включає цей заголовочний файл, і загальнодоступний символ буде експортований для функції. Отже, якщо функції additup визначені в header.h, а foo.c і bar.c обидва включають header.h, то foo.o і bar.o обидва включатимуть копії additup.
Коли ви перейдете до з'єднання цих двох об'єктних файлів разом, лінкер побачить, що додавання символів визначено не один раз, і не дозволить.
Якщо ви визнаєте функцію статичною, жоден символ не експортується. Об'єктні файли foo.o та bar.o як і раніше будуть містити окремі копії коду для функції, і вони зможуть ними користуватися, але лінкер не зможе побачити жодної копії функції, тому не скаржиться. Звичайно, жоден інший модуль також не зможе побачити функцію. І ваша програма буде роздута двома однаковими копіями тієї ж функції.
Якщо ви лише оголосите функцію у файлі заголовка, але не визначите її, а потім визначите її лише в одному модулі, тоді лінкер побачить одну копію функції, і кожен модуль у вашій програмі зможе побачити її і використай це. І ваша складена програма буде містити лише одну копію функції.
Отже, ви можете визначити визначення функції у файлі заголовка в C, це просто поганий стиль, погана форма та всебічна погана ідея.
(Під "оголосити" я маю на увазі надання прототипу функції без тіла; під "визначити" я маю на увазі надання фактичного коду функції функції; це стандартна термінологія С.)
#ifndef HEADER_H
має запобігати?
C і C ++ поводяться дуже однаково в цьому плані - ви можете мати inline
функції в заголовках. У C ++ будь-який метод, тіло якого знаходиться всередині визначення класу, неявно inline
. Якщо ви хочете зробити те ж саме в C, оголосіть функції static inline
.
static inline
" ... і ви все одно будете мати кілька копій функції у кожному блоці перекладу, який її використовує. У C ++ з нефункціональною static
inline
функцією у вас буде лише одна копія. Щоб реально мати реалізацію в заголовку в C, ви повинні 1) позначити реалізацію як inline
(наприклад inline void func(){do_something();}
) та 2) насправді сказати, що ця функція буде в якомусь певному блоці перекладу (наприклад void func();
).
Поняття файлу заголовка потребує невеликого пояснення:
Або ви дасте файл у командному рядку компілятора, або виконайте "#include". Більшість компіляторів приймають командний файл із розширенням c, C, cpp, c ++ тощо як вихідний файл. Однак вони зазвичай включають в себе командний рядок, щоб дозволити також використовувати будь-яке довільне розширення до вихідного файлу.
Зазвичай файл, що задається в командному рядку, називається "Джерело", а той, що входить, називається "Заголовок".
Крок препроцесора фактично забирає їх усіх і робить все, що видається компілятору як один великий файл. Те, що було в заголовку чи в джерелі, насправді не має значення в цьому пункті. Зазвичай існує варіант компілятора, який може показати результат цього етапу.
Отже, для кожного файлу, який був наданий у командному рядку компілятора, величезний файл надається компілятору. Це може мати код / дані, які будуть займати пам'ять та / або створювати символ, на який слід посилатися з інших файлів. Тепер кожен із них генерує зображення «об’єкта». Лінкер може дати "дублікат символу", якщо той самий символ знайдений у більш ніж двох об'єктних файлах, які пов'язані між собою. Можливо, це причина; не рекомендується вводити код у файл заголовка, який може створювати символи в об’єктному файлі.
"Вбудовані", як правило, вбудовані .., але при налагодженні вони можуть бути не накреслені. То чому лінкер не дає багатозначно визначених помилок? Просте ... Це "слабкі" символи, і поки всі дані / код для слабкого символу з усіх об'єктів мають однаковий розмір і вміст, пов'язані файли зберігатимуть одну копію та видалення копії з інших об'єктів. Це працює.
Стандартні цитати C ++
C ++ 17 N4659 проект стандарту 10.1.6 «рядний специфікатор» говорить , що методи неявно рядний:
4 Функція, визначена у визначенні класу, є вбудованою функцією.
а далі далі ми бачимо, що вбудовані методи не тільки можуть, але й мають бути визначені для всіх перекладацьких одиниць:
6 Вбудована функція або змінна повинна визначатися в кожній одиниці перекладу, в якій вона використовується, і має мати точно таке ж визначення у кожному випадку (6.2).
Про це також прямо зазначено в примітці під 12.2.1 "Функції членів":
1 Функція-член може бути визначена (11.4) у своєму визначенні класу, в цьому випадку це функція вбудованого члена (10.1.6) [...]
3 [Примітка. У програмі може бути якнайбільше одне визначення функції, яка не є вбудованою. У програмі може бути більше одного визначення функції вбудованого члена. Див. 6.2 та 10.1.6. - кінцева примітка]
Впровадження GCC 8.3
main.cpp
struct MyClass {
void myMethod() {}
};
int main() {
MyClass().myMethod();
}
Складіть і перегляньте символи:
g++ -c main.cpp
nm -C main.o
вихід:
U _GLOBAL_OFFSET_TABLE_
0000000000000000 W MyClass::myMethod()
U __stack_chk_fail
0000000000000000 T main
то ми бачимо, man nm
що MyClass::myMethod
символ позначений як слабкий на файлах об’єктів ELF, що означає, що він може відображатися в декількох файлах об'єктів:
"W" "w" Символ - це слабкий символ, який конкретно не позначений як символ слабкого об'єкта. Коли слабкий визначений символ пов'язаний із нормально визначеним символом, нормально визначений символ використовується без помилок. Коли слабкий невизначений символ пов'язаний і символ не визначений, значення символу визначається систематично без помилок. У деяких системах великі регістри вказують на те, що вказано значення за замовчуванням.
Можливо, з тієї ж причини, що ви повинні покласти повну реалізацію методу всередині визначення класу на Java.
Вони можуть виглядати схоже з чітко поставленими дужками та багатьма однаковими ключовими словами, але вони різні мови.