【问题标题】:Why are some functions declared extern and header file not included in source in Git source code?为什么 Git 源代码中的源代码中没有包含声明为 extern 和头文件的某些函数?
【发布时间】:2013-08-11 11:20:02
【问题描述】:

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

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

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

我有两个问题:

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

  2. 为什么“strbuf.h”没有#included in strbuf.c?

我作为一个新手程序员一直都知道你在 .c 文件中编写函数定义,而函数声明、宏、内联等都写在 .h 文件中,然后 #included 在每个想要的 .c 文件中使用这些功能等。

谁能解释一下?

【问题讨论】:

标签: c git coding-style


【解决方案1】:

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。但是你总是可以再写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 关键字注释“全局”(即文件范围、静态持续时间、外部链接)变量的原因是为了防止该特定声明成为定义。当多次定义相同的名称时,使用所谓的“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;

在这种情况下,其中一个(并且只有一个).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);

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

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

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

这始终是个好主意,原因很简单:编译器会将标头中f()声明f() 中的定义进行比较编码。如果两者不匹配(出于任何原因——通常是初始编码中的错误,或者在维护期间未能更新两者之一,但有时仅仅是由于 Cat Walked On Keyboard Syndrome 或类似原因),编译器可以捕获错误在编译时。


11999 年的 C 标准规定,在函数声明中省略 extern 关键字与在其中使用 extern 关键字的含义相同。这更容易描述,并且意味着您获得了已定义(和合理)的行为,而不是未定义的(因此可能是好的可能是坏的行为)。

【讨论】:

  • 我们是否应该假设变量部分的警告编写这种代码的唯一原因是混淆其他程序员,所以不要这样做。 :-) 也适用于函数部分static int foo(void); extern int foo(void);?
  • @AndréNeves:函数不能在其他函数中定义(至少在标准 C 中:GnuC 实际上允许这样做),这限制了损害,但是可以。 :-)
  • 试过 static int foo(void); int foo(void); 但 gcc 没有错误。
  • @Deqing:这似乎已在 C99 中得到修复,其中既未使用 static 也未使用 extern 声明的函数就像使用 extern 声明一样。这与 1989 ANSI / 1990 ISO 标准完全兼容,因为这些标准未定义行为。 C89/C90 中不需要警告或错误,现代 C 中也不需要任何警告或错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-12
  • 1970-01-01
  • 1970-01-01
  • 2015-05-07
  • 1970-01-01
  • 2014-09-03
相关资源
最近更新 更多