В C вы не можете иметь определение / реализацию функции внутри заголовочного файла. Тем не менее, в C ++ вы можете иметь полную реализацию метода внутри заголовочного файла. Почему поведение отличается?
В C вы не можете иметь определение / реализацию функции внутри заголовочного файла. Тем не менее, в C ++ вы можете иметь полную реализацию метода внутри заголовочного файла. Почему поведение отличается?
Ответы:
В C, если вы определите функцию в файле заголовка, то эта функция появится в каждом компилируемом модуле, который включает этот файл заголовка, и для этой функции будет экспортирован открытый символ. Так, если функция additup определена в header.h, и foo.c и bar.c оба включают header.h, то foo.o и bar.o будут включать копии additup.
Когда вы перейдете связать эти два объектных файла вместе, компоновщик увидит, что символ 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 ++ 17 N4659 10.1.6 « Встроенный спецификатор» говорится, что методы неявно встроены:
4 Функция, определенная в определении класса, является встроенной функцией.
и далее далее мы видим, что встроенные методы не только могут, но и должны быть определены во всех единицах перевода:
6 Встроенная функция или переменная должна быть определена в каждой единице перевода, в которой она используется odr, и должна иметь точно такое же определение в каждом случае (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.
Они могут выглядеть одинаково, с извилистыми скобками и многими одинаковыми ключевыми словами, но это разные языки.