Ответ 1
strbuf.c
включает cache.h
и cache.h
включает strbuf.h
, поэтому ваша предпосылка для вопроса 2 (что strbuf.c
не включает strbuf.h
) неверна: она включает его, а не напрямую.
extern
применяется к функциям
Ключевое слово extern
никогда не требуется для объявлений функций, но оно имеет эффект: оно объявляет, что идентификатор, назвавший функцию (то есть имя функции), имеет ту же связь, что и любое ранее видимое объявление, или если нет такое объявление видно, что идентификатор имеет внешнюю связь. Эта довольно запутанная фраза действительно означает, что, учитывая:
static int foo(void); extern int foo(void);
второе объявление foo
также объявляет его static
, давая ему внутреннюю связь. Если вы пишете:
static int foo(void); int foo(void); /* wrong in 1990s era C */
вы сначала объявили его как имеющую внутреннюю связь, а затем вторым как имеющую внешнюю связь, а в версиях до версии 1999 года C, 1 которая создает поведение undefined. В некотором смысле, ключевое слово extern
добавляет некоторую безопасность (по цене путаницы), поскольку это может означать static
, когда это необходимо. Но вы всегда можете написать static
снова, а extern
не является панацеей:
extern int foo(void); static int foo(void); /* ERROR */
Эта третья форма по-прежнему ошибочна. В первом объявлении extern
нет предыдущего видимого объявления, поэтому foo
имеет внешнюю связь, а затем второе объявление static
дает foo
внутреннюю связь, создавая поведение undefined.
Короче говоря, extern
не требуется для объявлений функций. Некоторые люди просто предпочитают это по причинам стиля.
(Примечание: я оставляю extern inline
на C99, что является довольно странным, а реализации различаются. См. http://www.greenend.org.uk/rjk/2003/03/inline.html для более подробной информации.)
extern
применяется к объявлениям переменных
Ключевое слово extern
в объявлении переменной имеет несколько разных эффектов. Во-первых, как и в случае с объявлениями функций, это влияет на привязку идентификатора. Во-вторых, для идентификатора вне любой функции ( "глобальная переменная" в одном из двух обычных чувств) это объявление является объявлением, а не определением, если переменная также не инициализирована.
Для переменных внутри функции (т.е. с "областью блока" ), например somevar
в:
void f(void) {
extern int somevar;
...
}
ключевое слово extern
заставляет идентификатор иметь некоторую привязку (внутреннюю или внешнюю) вместо "без привязки" (как для локальных переменных с автоматической продолжительностью). При этом он также приводит к тому, что сама переменная имеет статическую продолжительность, а не автоматическую. (Автоматические переменные продолжительности никогда не имеют привязки и всегда имеют область блока, а не область файла.)
Как и в случае с объявлениями функций, привязка extern
присваивается внутренней, если есть предыдущая видимая декларация внутренней привязки и внешняя в противном случае. Таким образом, x
внутри f()
имеет внутреннюю связь, несмотря на ключевое слово extern
:
static int x;
void f(void) {
extern int x; /* note: don't do this */
...
}
Единственная причина для написания такого кода - запутать других программистов, поэтому не делайте этого.: -)
В общем, причина аннотирования переменных "глобальная" (т.е. файловая область, статическая длительность, внешняя привязка) с ключевым словом extern
заключается в том, чтобы не дать этому конкретному объявлению стать определением. Компиляторы C, использующие так называемую "def/ref" модель, получают неудовлетворение при времени ссылки, когда одно и то же имя определяется более одного раза. Таким образом, если file1.c
говорит, что int globalvar;
и file2.c
также говорит int globalvar;
, оба являются определениями, и код не может компилироваться (хотя большинство Unix-подобных систем по умолчанию используют так называемую "общую модель", что делает эта работа в любом случае). Если вы объявляете такую переменную в файле заголовка, который, вероятно, будет включен из многих разных файлов .c
, используйте extern
, чтобы сделать это объявление "просто декларацией".
Один и только один из этих файлов .c
может снова объявить эту переменную, оставив ключевое слово extern
и/или включив инициализатор. Или некоторые люди предпочитают стиль, в котором заголовочный файл использует что-то вроде этого:
/* foo.h */
#ifndef EXTERN
# define EXTERN extern
#endif
EXTERN int globalvar;
В этом случае один (и только один) из этих файлов .c
может содержать последовательность:
#define EXTERN
#include "foo.h"
Здесь, поскольку extern
определен, #ifndef
отключает последующий #define
, а строка EXTERN int globalvar;
расширяется до просто int globalvar;
, так что это становится определением, а не объявлением. Лично мне не нравится этот стиль кодирования, хотя он действительно удовлетворяет принципу "не повторяй себя". В основном я считаю заглавную extern
вводящей в заблуждение, и этот шаблон бесполезен при инициализации. Те, кто предпочитает это, обычно завершают добавление второго макроса, чтобы скрыть инициализаторы:
#ifndef EXTERN
# define EXTERN extern
# define INIT_VAL(x) /*nothing*/
#else
# define INIT_VAL(x) = x
#endif
EXTERN int globalvar INIT_VAL(42);
но даже это разваливается, когда для элемента, который требуется инициализировать, требуется составной инициализатор (например, a struct
, который должен быть инициализирован до { 42, 23, 17, "hike!" }
).
(Примечание: я намеренно замалчивал все "предварительное определение" здесь. Определение без инициализатора только "предварительно определено" до конца единицы перевода. Это позволяет использовать некоторые виды передовых ссылок, которые иначе слишком сложно выразить. Это обычно не очень важно.)
, включая заголовок, объявляющий функцию f
в коде, который определяет функцию f
Это всегда хорошая идея по одной простой причине: компилятор сравнивает объявление f()
в заголовке с определением f()
в коде. Если они не совпадают (по какой-либо причине - обычно ошибка в первоначальном кодировании или отказ обновить один из двух во время обслуживания, но иногда просто из-за синдрома Cat Walked On Keyboard или аналогичного), компилятор может поймать ошибку во время компиляции.
1 В стандарте 1999 C указано, что исключение ключевого слова extern
в объявлении функции означает то же самое, что и ключевое слово extern
. Это гораздо проще описать и означает, что вы определяете (и разумно) поведение вместо undefined (и, следовательно, возможно, хорошее, может быть, плохое поведение).