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

时间:2013-08-11 11:20:03

标签: c git coding-style

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

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

这两个文件显然使用this documentation定义了一个API。

我有两个问题:

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

  2. 为什么“strbuf.h”在strbuf .c中不是#include?

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

    有人可以解释一下吗?

1 个答案:

答案 0 :(得分:24)

strbuf.c包括cache.hcache.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。但是你总是可以再次写staticextern不是灵丹妙药:

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

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

一般来说,注释&#34; global&#34; (即,带有file1.c关键字的文件范围,静态持续时间,外部链接)变量是为了防止该特定声明成为定义。 C编译器使用所谓的&#34; def / ref&#34;当多次定义同一名称时,模型会在链接时获得消化不良。因此,如果int globalvar;file2.cint globalvar;也说.c,则两者都是定义,代码可能无法编译(尽管大多数类Unix系统都使用所谓的& #34;普通模型&#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关键字相同。这个描述要简单得多,意味着你得到了定义的(和明智的)行为而不是未定义的行为(因此可能是好的可能不好的行为)。