Объявление (declaration) и определение (definition) в C и C++ имеют небольшое, но очень важное различие. Если не понимать его – то вас ожидают странные ошибки линкера (LD в GCC – /usr/bin/ld
), вроде “undefined symbol foo“, “undefined reference to ‘foo’” или даже “undefined reference to vtable for foo” (в C++).
Содержание
Что такое Объявление в C и C++
Когда вы объявляете переменную, функцию или класс, то все, что вы делаете – это просто говорите, что “вот есть что-то с таким-то именем и таким-то типом”. Далее компилятор (/usr/bin/gcc
или /usr/bin/cc
) может обработать все (но не все) случаи использования этого имени без необходимости полного описания этого имени. Объявление – без описания – позволяет писать понятный компилятору код, который он может понять без необходимости собирать все детали. Это особенно удобно когда вы работаете с кодом, который разбит на много файлов с исходным кодом и вам необходимо использовать функцию во многих из них. Вам не требуется писать все тело функции в каждом файле – но вам потребуется внести объявление об этой функции.
Итак, что такое Объявление? Например:
int func();
Это – объявление функции. В нем нет тела функции, но компилятор будет знать что он может использовать это имя для доступа к этой функции, ожидая что она уже описана где-то в другом месте.
Что такое Описание в C и C++
Описание (или определение – defining) чего-то означает предоставление всей необходимой информации для создания этого чего-то. Описание функции означает предоставление тела функции. Описание класса – код всех его методов и атрибутов. Как только что-то описано – оно может считаться так же и объявленным. Вы часто можете встретить и объявление и описание функции, класса или переменной одновременно.
Например – только описания функции будет вполне достаточно для компилятора. Вы можете написать такой код:
#include <stdio.h> int func(); int main() { int x = func(); printf("X == %dn", x); } int func() { return 2; }
Так как компилятор знает значение, возвращаемое функцией func()
и количество аргументов функции из ее объявления – он может скомпилировать вызов к этой функции, даже при том, что у него еще нет ее описания. На деле – описание функции func()
может находиться вообще в другом файле.
Вы так же можете объявить класс без его описания:
class MyClass;
Однако код, которому потребуются детали класса MyCalss
не будет работать. Т.е. – вы не можете сделать что-то вроде такого:
class MyClass; MyClass an_object; class MyClass { int _a_field; };
Так как компилятору требуется знать размер переменной an_object
– но он не может сделать, исходя только из объявления класса MyClass()
.
Объявление и Описание с использованием Extern
Как правило, когда вы объявляете переменную – то вы так же предоставляете и ее описание. Что такое “объявление” на самом деле?
Это значит, что вы указываете компилятору на необходимость создания хранилища в памяти для этой переменной. Например:
int x; int main() { x = 3; }
Строка int x;
выполняет и объявление и описание переменной. Фактически она говорит: “Создать переменную с именем x
и типом integer
. Кроме того – это будет глобальная переменная, описанная в объектом файле, связанном с файлом исходного кода“.
Это значит, что кто-то может написать еще один файл с таким кодом:
extern int x; int func() { x = 3; }
Использование extern
тут – это объявление переменной, но не ее описание. Тут говорится, что хранилищем для переменной находится где-то в другом месте. Технически, вы даже можете использовать такой код:
extern int x; int func() { x = 3; } int x;
Теперь у вас есть объявление переменной x
в начале программы, и ее определение – внизу. Но как правило – extern
используется тогда, когда вам требуется получить доступ к переменной, которая объявлена в другом файле, а затем, после компиляции – слинковать два объектных файла вместе.
Использование extern
для объявления глобальной переменной весьма схоже с объявлением функций в заголовочных файлах (по факту – вам намного чаще придется использовать extern
в заголовочных файлах, чем в исходном коде).
Кроме того – если вы разместите переменную в заголовочном файле и не будете использовать extern
– вы столкнетесь с проблемой “неопределенного символа” (undefined symbol): у вас будет символ с несколькими определениями, что приведет к ошибке вида “redefinition of ‘foo’“, когда линкер начнет выполнять связывание нескльких объектных файлов.
Объявление (declaration) vs определение (definition): итог
Объявление предоставляет базовые атрибуты символа – его тип и имя. Определение включает в себя все остальные детали этого символа: если это функция – то описание ее действий, если класс – то его методы и атрибуты, если переменная – то место ее размещения. Зачастую компилятору требуется только объявление чего-либо для компиляции файла в объектный файл. В таких случаях компилятор ожидает, что линкер сможет сам найти определение в другом файле. Если не один файл не содержит описания символа, но в коде есть его объявление – вы получите сообщение об ошибке “undefined symbol” во время линковки.
Примеры использования
Если вы хотите использовать функцию в нескольких файлах исходного кода – вы должны объявить эту функцию в одном заголовочном файле (.h
) а затем добавить ее описание в файле исходного кода (.c
или .cpp
). Весь код, который использует эту функцию должен включать в себе этот заголовочный файл.
Если вы хотите использовать некий класс в нескольких файлах – вам необходимо добавить его определение в заголовочный файл а его методы – определить в соответствующем файле исходного кода.
Если вы хотите использовать переменную в нескольких файлах – объявите ее с помощью ключевого слова extern
в заголовочном файле, а затем подключите этот заголовочный файл в файлы исходного кода. Затем вам необходимо добавить определение этой переменной в файле исходного кода.
Оригинал и больше докментации – тут>>>.