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

时间:2022-03-03 15:06:56

I wanted to see the source code of a real world application to understand good programming practices etc. So I chose Git and downloaded the source for version 1.8.4.

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

After randomly browsing through various files something caught my attention in these two files: strbuf.h strbuf.c

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

These two files apparently define an API with this documentation.

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

I have two questions :

我有两个问题:

  1. Why the function declarations at line 16,17,18,19 & global variable at line 6 in 'strbuf.h' declared extern ?

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

  2. Why "strbuf.h" is not #included in strbuf .c ?

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

I as a novice programmer have always learned that you write function definitions in a .c file whereas the function declarations,macros,inlines etc. are written in a .h file which is then #included in every .c file which wants to use these functions etc.

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

Can anyone please explain this?

有人可以解释一下吗?

1 个解决方案

#1


24  

strbuf.c includes cache.h and cache.h includes strbuf.h, so your premise for question 2 (that strbuf.c does not include strbuf.h) is wrong: it does include it, just not directly.

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

extern applied to functions

The extern keyword is never required for function declarations, but it does have an effect: it declares that the identifier naming the function (i.e., the function's name) has the same linkage as any previously visible declaration, or if no such declaration is visible, that the identifier has external linkage. This rather confusing phrasing really means that, given:

函数声明从不需要extern关键字,但它确实有效:它声明命名函数的标识符(即函数的名称)与任何先前可见的声明具有相同的链接,或者如果没有这样的声明可见,标识符有外部链接。这种相当混乱的措辞确实意味着,给出:

static int foo(void); extern int foo(void);

the second declaration of foo also declares it static, giving it internal linkage. If you write:

foo的第二个声明也声明它是静态的,赋予它内部联系。如果你写:

static int foo(void); int foo(void); /* wrong in 1990s era C */

you have declared it first as having internal linkage, and then second as having external linkage, and in pre-1999 versions of C,1 that produces undefined behavior. In one sense, then, the extern keyword adds some safety (at the price of confusion) as it can mean static when necessary. But you could always write static again, and extern is not a panacea:

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

extern int foo(void); static int foo(void); /* ERROR */

This third form is still erroneous. The first extern declaration has no previous visible declaration, so foo has external linkage, and then the second static declaration gives foo internal linkage, producing undefined behavior.

这第三种形式仍然是错误的。第一个extern声明没有先前的可见声明,所以foo有外部链接,然后第二个静态声明给foo内部链接,产生未定义的行为。

In short, extern is not required on function declarations. Some people just prefer it for style reasons.

简而言之,函数声明不需要extern。有些人因为风格原因而更喜欢它。

(Note: I'm leaving out extern inline in C99, which is kind of weird, and implementations vary. See http://www.greenend.org.uk/rjk/2003/03/inline.html for more details.)

(注意:我在C99中省略了外部内联,这有点奇怪,实现方式各不相同。有关详细信息,请参阅http://www.greenend.org.uk/rjk/2003/03/inline.html。)

extern applied to variable declarations

The extern keyword on a variable declaration has multiple different effects. First, as with function declarations, it affects the linkage of the identifier. Second, for an identifier outside any function (a "global variable" in one of the two usual senses), it causes the declaration to be a declaration, rather than a definition, provided the variable is not also initialized.

变量声明中的extern关键字具有多种不同的效果。首先,与函数声明一样,它会影响标识符的链接。其次,对于任何函数之外的标识符(两种常见意义之一中的“全局变量”),如果变量未初始化,则会使声明成为声明而不是定义。

For variables inside a function (i.e., with "block scope"), such as somevar in:

对于函数内部的变量(即“块范围”),例如somevar:

void f(void) {
    extern int somevar;
    ...
}

the extern keyword causes the identifier to have some linkage (internal or external) instead of "no linkage" (as for automatic-duration local variables). In the process, it also causes the variable itself to have static duration, rather than automatic. (Automatic-duration variables never have linkage, and always have block scope, rather than file scope.)

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

As with function declarations, the linkage extern assigns is internal if there is a previous visible internal-linkage declaration, and external otherwise. So the x inside f() here has internal linkage, despite the extern keyword:

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

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

The only reason to write this kind of code is to confuse other programmers, so don't do it. :-)

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

In general, the reason to annotate "global" (i.e., file-scope, static-duration, external-linkage) variables with the extern keyword is to prevent that particular declaration from becoming a definition. C compilers that use the so-called "def/ref" model get indigestion at link time when the same name is defined more than once. Thus, if file1.c says int globalvar; and file2.c also says int globalvar;, both are definitions and the code may not compile (although most Unix-like systems use the so-called "common model" by default, which makes this work anyway). If you are declaring such a variable in a header file—which is likely to be included from many different .c files—use extern to make that declaration "just a declaration".

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

One, and only one, of those .c files can then declare the variable again, leaving off the extern keyword and/or including an initializer. Or, some people prefer a style in which the header file uses something like this:

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

/* foo.h */
#ifndef EXTERN
# define EXTERN extern
#endif
EXTERN int globalvar;

In this case, one (and only one) of those .c files can contain the sequence:

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

#define EXTERN
#include "foo.h"

Here, since EXTERN is defined, the #ifndef turns off the subsequent #define and the line EXTERN int globalvar; expands to just int globalvar; so that this becomes a definition rather than a declaration. Personally, I dislike this coding style, although it does satisfy the "don't repeat yourself" principle. Mostly I find the uppercase EXTERN misleading, and this pattern is unhelpful with initialization. Those who favor it usually wind up adding a second macro to hide the initializers:

这里,由于定义了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);

but even this falls apart when the item to be initialized needs a compound initializer (e.g., a struct that should be initialized to { 42, 23, 17, "hike!" }).

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

(Note: I've deliberately glossed over the whole "tentative definition" thing here. A definition without an initializer is only "tentatively defined" until the end of the translation unit. This allows certain kinds of forward-references that are otherwise too difficult to express. It's not normally very important.)

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

including the header that declares function f in code that defines function f

This is always a good idea, for one simple reason: the compiler will compare the declaration of f() in the header against the definition of f() in the code. If the two do not match (for any reason—typically a mistake in initial coding, or a failure to update one of the two during maintenance, but occasionally simply due to Cat Walked On Keyboard Syndrome or similar), the compiler can catch the mistake at compile time.

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


1The 1999 C standard says that omitting the extern keyword in a function declaration means the same thing as using the extern keyword there. This is much simpler to describe, and means you get defined (and sensible) behavior instead of undefined (and therefore maybe-good maybe-bad behavior).

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

#1


24  

strbuf.c includes cache.h and cache.h includes strbuf.h, so your premise for question 2 (that strbuf.c does not include strbuf.h) is wrong: it does include it, just not directly.

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

extern applied to functions

The extern keyword is never required for function declarations, but it does have an effect: it declares that the identifier naming the function (i.e., the function's name) has the same linkage as any previously visible declaration, or if no such declaration is visible, that the identifier has external linkage. This rather confusing phrasing really means that, given:

函数声明从不需要extern关键字,但它确实有效:它声明命名函数的标识符(即函数的名称)与任何先前可见的声明具有相同的链接,或者如果没有这样的声明可见,标识符有外部链接。这种相当混乱的措辞确实意味着,给出:

static int foo(void); extern int foo(void);

the second declaration of foo also declares it static, giving it internal linkage. If you write:

foo的第二个声明也声明它是静态的,赋予它内部联系。如果你写:

static int foo(void); int foo(void); /* wrong in 1990s era C */

you have declared it first as having internal linkage, and then second as having external linkage, and in pre-1999 versions of C,1 that produces undefined behavior. In one sense, then, the extern keyword adds some safety (at the price of confusion) as it can mean static when necessary. But you could always write static again, and extern is not a panacea:

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

extern int foo(void); static int foo(void); /* ERROR */

This third form is still erroneous. The first extern declaration has no previous visible declaration, so foo has external linkage, and then the second static declaration gives foo internal linkage, producing undefined behavior.

这第三种形式仍然是错误的。第一个extern声明没有先前的可见声明,所以foo有外部链接,然后第二个静态声明给foo内部链接,产生未定义的行为。

In short, extern is not required on function declarations. Some people just prefer it for style reasons.

简而言之,函数声明不需要extern。有些人因为风格原因而更喜欢它。

(Note: I'm leaving out extern inline in C99, which is kind of weird, and implementations vary. See http://www.greenend.org.uk/rjk/2003/03/inline.html for more details.)

(注意:我在C99中省略了外部内联,这有点奇怪,实现方式各不相同。有关详细信息,请参阅http://www.greenend.org.uk/rjk/2003/03/inline.html。)

extern applied to variable declarations

The extern keyword on a variable declaration has multiple different effects. First, as with function declarations, it affects the linkage of the identifier. Second, for an identifier outside any function (a "global variable" in one of the two usual senses), it causes the declaration to be a declaration, rather than a definition, provided the variable is not also initialized.

变量声明中的extern关键字具有多种不同的效果。首先,与函数声明一样,它会影响标识符的链接。其次,对于任何函数之外的标识符(两种常见意义之一中的“全局变量”),如果变量未初始化,则会使声明成为声明而不是定义。

For variables inside a function (i.e., with "block scope"), such as somevar in:

对于函数内部的变量(即“块范围”),例如somevar:

void f(void) {
    extern int somevar;
    ...
}

the extern keyword causes the identifier to have some linkage (internal or external) instead of "no linkage" (as for automatic-duration local variables). In the process, it also causes the variable itself to have static duration, rather than automatic. (Automatic-duration variables never have linkage, and always have block scope, rather than file scope.)

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

As with function declarations, the linkage extern assigns is internal if there is a previous visible internal-linkage declaration, and external otherwise. So the x inside f() here has internal linkage, despite the extern keyword:

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

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

The only reason to write this kind of code is to confuse other programmers, so don't do it. :-)

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

In general, the reason to annotate "global" (i.e., file-scope, static-duration, external-linkage) variables with the extern keyword is to prevent that particular declaration from becoming a definition. C compilers that use the so-called "def/ref" model get indigestion at link time when the same name is defined more than once. Thus, if file1.c says int globalvar; and file2.c also says int globalvar;, both are definitions and the code may not compile (although most Unix-like systems use the so-called "common model" by default, which makes this work anyway). If you are declaring such a variable in a header file—which is likely to be included from many different .c files—use extern to make that declaration "just a declaration".

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

One, and only one, of those .c files can then declare the variable again, leaving off the extern keyword and/or including an initializer. Or, some people prefer a style in which the header file uses something like this:

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

/* foo.h */
#ifndef EXTERN
# define EXTERN extern
#endif
EXTERN int globalvar;

In this case, one (and only one) of those .c files can contain the sequence:

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

#define EXTERN
#include "foo.h"

Here, since EXTERN is defined, the #ifndef turns off the subsequent #define and the line EXTERN int globalvar; expands to just int globalvar; so that this becomes a definition rather than a declaration. Personally, I dislike this coding style, although it does satisfy the "don't repeat yourself" principle. Mostly I find the uppercase EXTERN misleading, and this pattern is unhelpful with initialization. Those who favor it usually wind up adding a second macro to hide the initializers:

这里,由于定义了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);

but even this falls apart when the item to be initialized needs a compound initializer (e.g., a struct that should be initialized to { 42, 23, 17, "hike!" }).

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

(Note: I've deliberately glossed over the whole "tentative definition" thing here. A definition without an initializer is only "tentatively defined" until the end of the translation unit. This allows certain kinds of forward-references that are otherwise too difficult to express. It's not normally very important.)

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

including the header that declares function f in code that defines function f

This is always a good idea, for one simple reason: the compiler will compare the declaration of f() in the header against the definition of f() in the code. If the two do not match (for any reason—typically a mistake in initial coding, or a failure to update one of the two during maintenance, but occasionally simply due to Cat Walked On Keyboard Syndrome or similar), the compiler can catch the mistake at compile time.

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


1The 1999 C standard says that omitting the extern keyword in a function declaration means the same thing as using the extern keyword there. This is much simpler to describe, and means you get defined (and sensible) behavior instead of undefined (and therefore maybe-good maybe-bad behavior).

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