strbuf.c
包括cache.h
and cache.h
包括strbuf.h
,所以问题 2 的前提(即strbuf.c
不包括strbuf.h
) 是错误的:它确实包含它,只是不是直接包含它。
extern
应用于函数
The extern
函数声明从来不需要关键字,但它确实有作用:它声明命名函数的标识符(即函数的名称)与任何先前可见的声明具有相同的链接,或者如果没有这样的声明可见,则标识符具有外部链接。这个相当令人困惑的措辞实际上意味着,考虑到:
static int foo(void); extern int foo(void);
第二次声明foo
也声明了它static
,赋予其内部联系。如果你写:
static int foo(void); int foo(void); /* wrong in 1990s era C */
you have declared it first as having internal linkage, and then second as having external linkage, and in pre-1999 versions of C,1 that produces undefined behavior. In one sense, then, the extern
keyword adds some safety (at the price of confusion) as it can mean static
when necessary. But you could always write static
again, and extern
is not a panacea:
extern int foo(void); static int foo(void); /* ERROR */
这第三种形式仍然是错误的。首先extern
声明没有先前可见的声明,所以foo
有外部链接,然后是第二个static
声明给出foo
内部链接,产生未定义的行为。
简而言之,extern
函数声明中不需要。有些人只是出于风格原因更喜欢它。
(注:我省略了extern inline
在 C99 中,这有点奇怪,并且实现各不相同。看http://www.greenend.org.uk/rjk/2003/03/inline.html更多细节。)
extern
应用于变量声明
The extern
变量声明上的关键字具有多种不同的效果。首先,与函数声明一样,它会影响标识符的链接。其次,对于任何函数外部的标识符(两种通常意义上的“全局变量”),它会导致声明成为声明,而不是定义,前提是该变量没有被初始化。
对于函数内部的变量(即具有“块作用域”),例如somevar
in:
void f(void) {
extern int somevar;
...
}
the extern
关键字使标识符具有某种链接(内部或外部),而不是“无链接”(对于自动持续时间局部变量)。在这个过程中,也导致变量本身具有静态持续时间,而不是自动持续时间。 (自动持续时间变量从不具有链接,并且始终具有块作用域,而不是文件作用域。)
与函数声明一样,链接extern
如果存在先前可见的内部链接声明,则指派为内部,否则为外部。所以x
inside f()
这里有内部联系,尽管extern
关键词:
static int x;
void f(void) {
extern int x; /* note: don't do this */
...
}
编写这种代码的唯一原因是为了迷惑其他程序员,所以不要这样做。 :-)
一般来说,使用注释“全局”(即文件范围、静态持续时间、外部链接)变量的原因extern
关键字是为了防止该特定声明成为定义。当多次定义同一个名称时,使用所谓的“def/ref”模型的 C 编译器会在链接时出现消化不良。因此,如果file1.c
says int globalvar;
and 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);
但当要初始化的项需要复合初始化器(例如,struct
应该初始化为{ 42, 23, 17, "hike!" }
).
(注意:我在这里故意掩盖了整个“暂定定义”的事情。没有初始化程序的定义只是“暂定定义”,直到翻译单元结束。这允许某些类型的前向引用,否则这些引用太困难了来表达。这通常不是很重要。)
包括声明函数的标头f
在定义函数的代码中f
这始终是一个好主意,原因很简单:编译器将比较宣言 of f()
在标题中针对定义 of f()
在代码中。如果两者不匹配(出于任何原因 - 通常是初始编码中的错误,或者在维护期间未能更新两者之一,但偶尔只是由于猫走在键盘综合症或类似情况),编译器可以捕获错误在编译时。
1The 1999 C standard says that omitting the extern
keyword in a function declaration means the same thing as using the extern
keyword there. This is much simpler to describe, and means you get defined (and sensible) behavior instead of undefined (and therefore maybe-good maybe-bad behavior).