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 会产生未定义的行为。那么,在某种意义上,extern 关键字增加了一些安全性(以混淆为代价),因为它在必要时可以表示static。但是你总是可以再写static,而extern不是万能的:
extern int foo(void); static int foo(void); /* ERROR */
这第三种形式仍然是错误的。第一个 extern 声明之前没有可见声明,因此 foo 具有外部链接,然后第二个 static 声明提供 foo 内部链接,从而产生未定义的行为。
简而言之,函数声明不需要extern。有些人只是出于风格原因更喜欢它。
(注意:我在 C99 中省略了 extern inline,这有点奇怪,并且实现方式各不相同。有关更多详细信息,请参阅 http://www.greenend.org.uk/rjk/2003/03/inline.html。)
extern 应用于变量声明
变量声明中的extern 关键字具有多种不同的效果。首先,与函数声明一样,它会影响标识符的链接。其次,对于任何函数之外的标识符(两种通常意义上的“全局变量”之一),它会导致声明成为声明,而不是定义,前提是该变量还没有被初始化。
对于函数内部的变量(即具有“块作用域”),例如somevar in:
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 关键字注释“全局”(即文件范围、静态持续时间、外部链接)变量的原因是为了防止该特定声明成为定义。当多次定义相同的名称时,使用所谓的“def/ref”模型的 C 编译器会在链接时消化不良。因此,如果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);
但是当要初始化的项目需要一个复合初始化器(例如,一个应该初始化为 { 42, 23, 17, "hike!" } 的 struct)时,即使这样也会崩溃。
(注意:我在这里故意掩盖了整个“暂定定义”。没有初始化器的定义只是“暂定定义”,直到翻译单元结束。这允许某些类型的前向引用否则太难表达了。通常不是很重要。)
在定义函数f的代码中包括声明函数f的标头
这始终是个好主意,原因很简单:编译器会将标头中f() 的声明与f() 中的定义进行比较编码。如果两者不匹配(出于任何原因——通常是初始编码中的错误,或者在维护期间未能更新两者之一,但有时仅仅是由于 Cat Walked On Keyboard Syndrome 或类似原因),编译器可以捕获错误在编译时。
11999 年的 C 标准规定,在函数声明中省略 extern 关键字与在其中使用 extern 关键字的含义相同。这更容易描述,并且意味着您获得了已定义(和合理)的行为,而不是未定义的(因此可能是好的可能是坏的行为)。