为什么某些函数声明extern和头文件不包含在Git源代码的源代码中?

rsj*_*ani 8 c git coding-style

我想看一个真实世界的应用程序的源代码,以了解良好的编程实践等.所以我选择了Git并下载了1.8.4版本的源代码.

在随机浏览各种文件之后,我在这两个文件中引起了我的注意:strbuf.h strbuf.c

这两个文件显然使用此文档定义了API .

我有两个问题:

  1. 为什么第16,17,18,19行的函数声明和'strbuf.h'第6行的全局变量声明为extern?

  2. 为什么"strbuf.h"在strbuf .c中不是#included?

作为新手程序员,我总是学会在.c文件中编写函数定义,而函数声明,宏,内联等都是用.h文件编写的,然后在每个.c文件中#included都要使用这些文件.功能等

有人可以解释一下吗?

tor*_*rek 27

strbuf.c包含cache.hcache.h包含strbuf.h,所以你的问题2(strbuf.c不包括strbuf.h)的前提是错误的:它确实包括它,而不是直接.

extern 适用于功能

extern关键字永远不会要求函数声明,但它确实有一个作用:它声明的标识符命名的函数(即函数名)具有相同的链接任何以前可见的声明,或者如果没有这样的声明是可见的,即标识符具有外部链接.这种相当混乱的措辞真的意味着,给出:

static int foo(void); extern int foo(void);
Run Code Online (Sandbox Code Playgroud)

第二个宣言foo也宣布它static,给它内部联系.如果你写:

static int foo(void); int foo(void); /* wrong in 1990s era C */
Run Code Online (Sandbox Code Playgroud)

你首先将它声明为具有内部链接,然后将其声明为具有外部链接,并且在1999之前的C版本中,1产生未定义的行为.从某种意义上说,extern关键字增加了一些安全性(以混乱的代价),因为它可能意味着static必要时.但是你总是可以static再次写作,而extern不是灵丹妙药:

extern int foo(void); static int foo(void); /* ERROR */
Run Code Online (Sandbox Code Playgroud)

第三种形式仍然是错误的.第一个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;
    ...
}
Run Code Online (Sandbox Code Playgroud)

extern关键字使标识符有一些键(内部或外部),而不是"无连接"(如用于自动持续时间的局部变量).在此过程中,它还会使变量本身具有静态持续时间,而不是自动.(自动持续时间变量从不具有链接,并且始终具有块范围,而不是文件范围.)

与函数声明一样,extern如果存在先前可见的内部链接声明,则链接分配是内部的,否则是外部的.所以x内部f()有内部链接,尽管有extern关键字:

static int x;
void f(void) {
    extern int x; /* note: don't do this */
    ...
}
Run Code Online (Sandbox Code Playgroud)

编写这种代码的唯一原因是混淆其他程序员,所以不要这样做.:-)

通常,使用extern关键字注释"全局"(即文件范围,静态持续时间,外部链接)变量的原因是为了防止该特定声明成为定义.使用所谓的"def/ref"模型的C编译器在链接时会在多次定义相同名称时消失.因此,如果file1.cint globalvar;并且file2.c也说int globalvar;,两者都是定义,代码可能无法编译(尽管大多数类Unix系统默认使用所谓的"通用模型",这使得这项工作无论如何).如果你在头文件中声明这样一个变量 - 它可能包含在许多不同的.c文件中 - 用于extern使声明"只是一个声明".

.c然后,这些文件中的一个,并且只有一个可以再次声明该变量,而不使用extern关键字和/或包括初始化程序.或者,有些人更喜欢头文件使用这样的样式:

/* foo.h */
#ifndef EXTERN
# define EXTERN extern
#endif
EXTERN int globalvar;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,这些.c文件中的一个(并且只有一个)可以包含序列:

#define EXTERN
#include "foo.h"
Run Code Online (Sandbox Code Playgroud)

在这里,自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);
Run Code Online (Sandbox Code Playgroud)

但是,当要初始化的项目需要复合初始化器(例如,struct应该初始化为{ 42, 23, 17, "hike!" })时,即使这样也会分崩离析.

(注意:我在这里故意掩盖整个"暂定定义"的东西.没有初始化器的定义只是"暂时定义"直到翻译单元结束.这允许某些类型的前向引用,否则太难了表达.通常不是很重要.)

包括在f定义函数的代码中声明函数的头f

这始终是一个好主意,因为一个简单的原因:编译器将比较申报f()在头对定义f()代码.如果两者不匹配(由于任何原因 - 通常是初始编码中的错误,或者在维护期间未能更新其中一个,但偶尔仅仅因为Cat Walked On键盘综合症或类似问题),编译器可以捕获错误在编译时.


1 1999 C标准说extern在函数声明中省略关键字意味着在extern那里使用关键字.这更容易描述,意味着你得到了定义(和明智的)行为而不是未定义(因此可能是好的可能 - 坏行为).