strbuf.c
包括cache.h
和cache.h
包括,所以你对问题2(不包括)strbuf.h
的前提是错误的:它确实包括它,只是不直接。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
函数声明不需要。有些人只是出于风格原因更喜欢它。
(注意:我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
原因是为了防止该特定声明成为定义。当多次定义相同的名称时,使用所谓的“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;
扩展为just 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
应该初始化为的 a { 42, 23, 17, "hike!" }
)时,即使这样也会崩溃。
(注意:我在这里故意掩盖了整个“暂定定义”的东西。没有初始化器的定义只是“暂定定义”,直到翻译单元结束。这允许某些类型的前向引用,否则它们太难了表达。通常不是很重要。)
f
包括在定义函数的代码中声明函数的标头f
这总是一个好主意,原因很简单:编译器会将标头中的声明与代码中的定义进行比较。如果两者不匹配(出于任何原因——通常是初始编码中的错误,或者在维护期间未能更新两者之一,但有时仅仅是由于 Cat Walked On Keyboard Syndrome 或类似原因),编译器可以捕获错误在编译时。f()
f()
1 1999 年的 C 标准规定,在函数声明中省略extern
关键字与在其中使用关键字的含义相同extern
。这更容易描述,并且意味着您获得定义(和明智)的行为而不是未定义的行为(因此可能 - 好的可能 - 坏的行为)。