我想查看真实世界应用程序的源代码,以了解良好的编程实践等。所以我选择了Git并下载了1.8.4版本的源代码。
在随机浏览各种文件后,我在这两个文件中引起了注意:strbuf.h strbuf.c
这两个文件显然使用this documentation定义了一个API。
我有两个问题:
为什么第16,17,18,19和16行的函数声明'strbuf.h'第6行的全局变量声明为extern?
为什么“strbuf.h”在strbuf .c中不是#include? p>
我作为新手程序员总是学会在.c文件中编写函数定义,而函数声明,宏,内联等都写在.h文件中,然后在每个.c文件中都包含#included使用这些功能等。
有人可以解释一下吗?
答案 0 :(得分:24)
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
编写此类代码的唯一原因是混淆其他程序员,所以不要这样做。 : - )
一般来说,注释" global" (即,带有file1.c
关键字的文件范围,静态持续时间,外部链接)变量是为了防止该特定声明成为定义。 C编译器使用所谓的" def / ref"当多次定义同一名称时,模型会在链接时获得消化不良。因此,如果int globalvar;
说file2.c
而int globalvar;
也说.c
,则两者都是定义,代码可能无法编译(尽管大多数类Unix系统都使用所谓的& #34;普通模型"默认情况下,这使得这项工作无论如何都是如此)。如果要在头文件中声明这样的变量 - 可能包含在许多不同的extern
文件中 - 请使用.c
进行声明&#34;只需声明&#34;。< / p>
然后,这些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
,这样就会变成定义而不是声明。就个人而言,我不喜欢这种编码风格,虽然它确实满足了“不要重复自己”#34;原理。大多数情况下,我发现大写#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!" }
的{{1}})时,即使这样也会分崩离析。
(注意:我故意掩饰整个&#34;暂定的定义&#34;这里的事情。没有初始化器的定义只是&#34;暂定定义&#34;直到翻译结束这允许某些类型的前向参考,否则这些参考太难以表达。它通常不是很重要。)
f
f
的标头
这总是一个好主意,原因很简单:编译器会将标头中f()
的声明与{{的定义进行比较1}}在代码中。如果两者不匹配(由于任何原因 - 通常是初始编码中的错误,或者在维护期间未能更新其中一个,但偶尔仅仅因为Cat Walked On键盘综合症或类似问题),编译器可以捕获错误在编译时。
1 1999 C标准说,在函数声明中省略f()
关键字意味着与在那里使用extern
关键字相同。这个描述要简单得多,意味着你得到了定义的(和明智的)行为而不是未定义的行为(因此可能是好的可能不好的行为)。