如何使用extern在源文件之间共享变量?

942 c global-variables extern

我知道C中的全局变量有时会有extern关键字.什么是extern变量?宣言是什么样的?它的范围是什么?

这与跨源文件共享变量有关,但这是如何工作的?我在哪里用extern

Jon*_*ler 1681

extern当您正在构建的程序由链接在一起的多个源文件组成时,使用仅具有相关性,例如,在源文件中定义的某些变量file1.c需要在其他源文件中引用,例如file2.c.

了解定义变量和声明变量之间的区别非常重要:

  • 一个变量声明当编译器被告知一个变量存在(这是它的类型); 它不会在该点为变量分配存储空间.
  • 一个变量被定义时,编译器分配该变量的存储.

您可以多次声明变量(尽管一次就足够了); 您只能在给定范围内定义一次.变量定义也是一个声明,但并非所有变量声明都是定义.

声明和定义全局变量的最佳方式

声明和定义全局变量的干净,可靠的方法是使用头文件来包含变量的extern 声明.

标头包含在定义变量的一个源文件和引用该变量的所有源文件中.对于每个程序,一个源文件(和一个源文件)定义该变量.同样,一个头文件(只有一个头文件)应声明该变量.头文件至关重要; 它可以在独立的TU(翻译单元 - 思考源文件)之间进行交叉检查,并确保一致性.

虽然还有其他方法,但这种方法简单可靠.它是通过论证file3.h,file1.cfile2.c:

file3.h

extern int global_variable;  /* Declaration of the variable */
Run Code Online (Sandbox Code Playgroud)

在file1.c

#include "file3.h"  /* Declaration made available here */
#include "prog1.h"  /* Function declarations */

/* Variable defined here */
int global_variable = 37;    /* Definition checked against declaration */

int increment(void) { return global_variable++; }
Run Code Online (Sandbox Code Playgroud)

file2.c中

#include "file3.h"
#include "prog1.h"
#include <stdio.h>

void use_it(void)
{
    printf("Global variable: %d\n", global_variable++);
}
Run Code Online (Sandbox Code Playgroud)

这是声明和定义全局变量的最佳方式.


接下来的两个文件完成了以下内容prog1:

显示的完整程序使用函数,因此函数声明已经悄悄进入.C99和C11都要求在使用之前声明或定义函数(而C90没有,有充分的理由).我extern在标头中的函数声明前面使用关键字来保持一致性 - 以匹配extern标头中变量声明的前面.很多人不喜欢extern在功能声明面前使用; 编译器并不关心 - 最终,只要你是一致的,我也不会,至少在源文件中.

prog1.h

extern void use_it(void);
extern int increment(void);
Run Code Online (Sandbox Code Playgroud)

prog1.c的

#include "file3.h"
#include "prog1.h"
#include <stdio.h>

int main(void)
{
    use_it();
    global_variable += 19;
    use_it();
    printf("Increment: %d\n", increment());
    return 0;
}
Run Code Online (Sandbox Code Playgroud)
  • prog1使用prog1.c,file1.c,file2.c,file3.hprog1.h.

该文件prog1.mk只是一个makefile prog1.它将适用于make大约在千禧年之后生产的大多数版本.它与GNU Make没有特别的联系.

prog1.mk

# Minimal makefile for prog1

PROGRAM = prog1
FILES.c = prog1.c file1.c file2.c
FILES.h = prog1.h file3.h
FILES.o = ${FILES.c:.c=.o}

CC      = gcc
SFLAGS  = -std=c11
GFLAGS  = -g
OFLAGS  = -O3
WFLAG1  = -Wall
WFLAG2  = -Wextra
WFLAG3  = -Werror
WFLAG4  = -Wstrict-prototypes
WFLAG5  = -Wmissing-prototypes
WFLAGS  = ${WFLAG1} ${WFLAG2} ${WFLAG3} ${WFLAG4} ${WFLAG5}
UFLAGS  = # Set on command line only

CFLAGS  = ${SFLAGS} ${GFLAGS} ${OFLAGS} ${WFLAGS} ${UFLAGS}
LDFLAGS =
LDLIBS  =

all:    ${PROGRAM}

${PROGRAM}: ${FILES.o}
    ${CC} -o $@ ${CFLAGS} ${FILES.o} ${LDFLAGS} ${LDLIBS}

prog1.o: ${FILES.h}
file1.o: ${FILES.h}
file2.o: ${FILES.h}

# If it exists, prog1.dSYM is a directory on macOS
DEBRIS = a.out core *~ *.dSYM
RM_FR  = rm -fr

clean:
    ${RM_FR} ${FILES.o} ${PROGRAM} ${DEBRIS}
Run Code Online (Sandbox Code Playgroud)

方针

规则只能由专家打破,并且只有充分的理由:

  • 头文件仅包含extern变量的声明 - 从不 static或不合格的变量定义.
  • 对于任何给定的变量,只有一个头文件声明它(SPOT - 单点真相).
  • 源文件永远不会包含extern变量声明 - 源文件始终包含声明它们的(唯一)标头.
  • For any given variable, exactly one source file defines the variable, preferably initializing it too. (Although there is no need to initialize explicitly to zero, it does no harm and can do some good, because there can be only one initialized definition of a particular global variable in a program).
  • The source file that defines the variable also includes the header to ensure that the definition and the declaration are consistent.
  • A function should never need to declare a variable using extern.
  • Avoid global variables whenever possible — use functions instead.

The source code and text of this answer are available in my SOQ (Stack Overflow Questions) repository on GitHub in the src/so-0143-3204 sub-directory.

If you're not an experienced C programmer, you could (and perhaps should) stop reading here.

Not so good way to define global variables

With some (indeed, many) C compilers, you can get away with what's called a 'common' definition of a variable too. 'Common', here, refers to a technique used in Fortran for sharing variables between source files, using a (possibly named) COMMON block. What happens here is that each of a number of files provides a tentative definition of the variable. As long as no more than one file provides an initialized definition, then the various files end up sharing a common single definition of the variable:

file10.c

#include "prog2.h"

int i;   /* Do not do this in portable code */

void inc(void) { i++; }
Run Code Online (Sandbox Code Playgroud)

file11.c

#include "prog2.h"

int i;   /* Do not do this in portable code */

void dec(void) { i--; }
Run Code Online (Sandbox Code Playgroud)

file12.c

#include "prog2.h"
#include <stdio.h>

int i = 9;   /* Do not do this in portable code */

void put(void) { printf("i = %d\n", i); }
Run Code Online (Sandbox Code Playgroud)

This technique does not conform to the letter of the C standard and the 'one definition rule' — it is officially undefined behaviour:

J.2 Undefined behavior

An identifier with external linkage is used, but in the program there does not exist exactly one external definition for the identifier, or the identifier is not used and there exist multiple external definitions for the identifier (6.9).

§6.9 External definitions 5

An external definition is an external declaration that is also a definition of a function (other than an inline definition) or an object. If an identifier declared with external linkage is used in an expression (other than as part of the operand of a sizeof or _Alignof operator whose result is an integer constant), somewhere in the entire program there shall be exactly one external definition for the identifier; otherwise, there shall be no more than one.161)

161) Thus, if an identifier declared with external linkage is not used in an expression, there need be no external definition for it.

However, the C standard also lists it in informative Annex J as one of the Common extensions.

J.5.11 Multiple external definitions

There may be more than one external definition for the identifier of an object, with or without the explicit use of the keyword extern; if the definitions disagree, or more than one is initialized, the behavior is undefined (6.9.2).

Because this technique is not always supported, it is best to avoid using it, especially if your code needs to be portable. Using this technique, you can also end up with unintentional type punning. If one of the files declared i as a double instead of as an int, C's type-unsafe linkers probably would not spot the mismatch. If you're on a machine with 64-bit int and double, you'd not even get a warning; on a machine with 32-bit int and 64-bit double, you'd probably get a warning about the different sizes — the linker would use the largest size, exactly as a Fortran program would take the largest size of any common blocks.


The next two files complete the source for prog2:

prog2.h

extern void dec(void);
extern void put(void);
extern void inc(void);
Run Code Online (Sandbox Code Playgroud)

prog2.c

#include "prog2.h"
#include <stdio.h>

int main(void)
{
    inc();
    put();
    dec();
    put();
    dec();
    put();
}
Run Code Online (Sandbox Code Playgroud)
  • prog2 uses prog2.c, file10.c, file11.c, file12.c, prog2.h.

Warning

As noted in comments here, and as stated in my answer to a similar question, using multiple definitions for a global variable leads to undefined behaviour (J.2; §6.9), which is the standard's way of saying "anything could happen". One of the things that can happen is that the program behaves as you expect; and J.5.11 says, approximately, "you might be lucky more often than you deserve". But a program that relies on multiple definitions of an extern variable — with or without the explicit 'extern' keyword — is not a strictly conforming program and not guaranteed to work everywhere. Equivalently: it contains a bug which may or may not show itself.

Violating the guidelines

There are, of course, many ways in which these guidelines can be broken. Occasionally, there may be a good reason to break the guidelines, but such occasions are extremely unusual.

faulty_header.h

int some_var;    /* Do not do this in a header!!! */
Run Code Online (Sandbox Code Playgroud)

Note 1: if the header defines the variable without the extern keyword, then each file that includes the header creates a tentative definition of the variable. As noted previously, this will often work, but the C standard does not guarantee that it will work.

broken_header.h

int some_var = 13;    /* Only one source file in a program can use this */
Run Code Online (Sandbox Code Playgroud)

Note 2: if the header defines and initializes the variable, then only one source file in a given program can use the header. Since headers are primarily for sharing information, it is a bit silly to create one that can only be used once.

seldom_correct.h

static int hidden_global = 3;   /* Each source file gets its own copy  */
Run Code Online (Sandbox Code Playgroud)

Note 3: if the header defines a static variable (with or without initialization), then each source file ends up with its own private version of the 'global' variable.

If the variable is actually a complex array, for example, this can lead to extreme duplication of code. It can, very occasionally, be a sensible way to achieve some effect, but that is very unusual.


Summary

Use the header technique I showed first. It works reliably and everywhere. Note, in particular, that the header declaring the global_variable is included in every file that uses it — including the one that defines it. This ensures that everything is self-consistent.

Similar concerns arise with declaring and defining functions — analogous rules apply. But the question was about variables specifically, so I've kept the answer to variables only.

End of Original Answer

If you're not an experienced C programmer, you probably should stop reading here.


Late Major Addition

Avoiding Code Duplication

One concern that is sometimes (and legitimately) raised about the 'declarations in headers, definitions in source' mechanism described here is that there are two files to be kept synchronized — the header and the source. This is usually followed up with an observation that a macro can be used so that the header serves double duty — normally declaring the variables, but when a specific macro is set before the header is included, it defines the variables instead.

Another concern can be that the variables need to be defined in each of a number of 'main programs'. This is normally a spurious concern; you can simply introduce a C source file to define the variables and link the object file produced with each of the programs.

A typical scheme works like this, using the original global variable illustrated in file3.h:

file3a.h

#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#else
#define EXTERN extern
#endif /* DEFINE_VARIABLES */

EXTERN int global_variable;
Run Code Online (Sandbox Code Playgroud)

file1a.c

#define DEFINE_VARIABLES
#include "file3a.h"  /* Variable defined - but not initialized */
#include "prog3.h"

int increment(void) { return global_variable++; }
Run Code Online (Sandbox Code Playgroud)

file2a.c

#include "file3a.h"
#include "prog3.h"
#include <stdio.h>

void use_it(void)
{
    printf("Global variable: %d\n", global_variable++);
}
Run Code Online (Sandbox Code Playgroud)

The next two files complete the source for prog3:

prog3.h

extern void use_it(void);
extern int increment(void);
Run Code Online (Sandbox Code Playgroud)

prog3.c

#include "file3a.h"
#include "prog3.h"
#include <stdio.h>

int main(void)
{
    use_it();
    global_variable += 19;
    use_it();
    printf("Increment: %d\n", increment());
    return 0;
}
Run Code Online (Sandbox Code Playgroud)
  • prog3 uses prog3.c, file1a.c, file2a.c, file3a.h, prog3.h.

Variable initialization

The problem with this scheme as shown is that it does not provide for initialization of the global variable. With C99 or C11 and variable argument lists for macros, you could define a macro to support initialization too. (With C89 and no support for variable argument lists in macros, there is no easy way to handle arbitrarily long initializers.)

file3b.h

#ifdef DEFINE_VARIABLES
#define EXTERN                  /* nothing */
#define INITIALIZER(...)        = __VA_ARGS__
#else
#define EXTERN                  extern
#define INITIALIZER(...)        /* nothing */
#endif /* DEFINE_VARIABLES */

EXTERN int global_variable INITIALIZER(37);
EXTERN struct { int a; int b; } oddball_struct INITIALIZER({ 41, 43 });
Run Code Online (Sandbox Code Playgroud)

Reverse contents of #if and #else blocks, fixing bug identified by Denis Kniazhev

file1b.c

#define DEFINE_VARIABLES
#include "file3b.h"  /* Variables now defined and initialized */
#include "prog4.h"

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
Run Code Online (Sandbox Code Playgroud)

file2b.c

#include "file3b.h"
#include "prog4.h"
#include <stdio.h>

void use_them(void)
{
    printf("Global variable: %d\n", global_variable++);
    oddball_struct.a += global_variable;
    oddball_struct.b -= global_variable / 2;
}
Run Code Online (Sandbox Code Playgroud)

Clearly, the code for the oddball structure is not what you'd normally write, but it illustrates the point. The first argument to the second invocation of INITIALIZER is { 41 and the remaining argument (singular in this example) is 43 }. Without C99 or similar support for variable argument lists for macros, initializers that need to contain commas are very problematic.

Correct header file3b.h included (instead of fileba.h) per Denis Kniazhev


The next two files complete the source for prog4:

prog4.h

extern int increment(void);
extern int oddball_value(void);
extern void use_them(void);
Run Code Online (Sandbox Code Playgroud)

prog4.c

#include "file3b.h"
#include "prog4.h"
#include <stdio.h>

int main(void)
{
    use_them();
    global_variable += 19;
    use_them();
    printf("Increment: %d\n", increment());
    printf("Oddball:   %d\n", oddball_value());
    return 0;
}
Run Code Online (Sandbox Code Playgroud)
  • prog4 uses prog4.c, file1b.c, file2b.c, prog4.h, file3b.h.

Header Guards

Any header should be protected against reinclusion, so that type definitions (enum, struct or union types, or typedefs generally) do not cause problems. The standard technique is to wrap the body of the header in a header guard such as:

#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED

...contents of header...

#endif /* FILE3B_H_INCLUDED */
Run Code Online (Sandbox Code Playgroud)

The header might be included twice indirectly. For example, if file4b.h includes file3b.h for a type definition that isn't shown, and file1b.c needs to use both header file4b.h and file3b.h, then you have some more tricky issues to resolve. Clearly, you might revise the header list to include just file4b.h. However, you might not be aware of the internal dependencies — and the code should, ideally, continue to work.

Further, it starts to get tricky because you might include file4b.h before including file3b.h to generate the definitions, but the normal header guards on file3b.h would prevent the header being reincluded.

So, you need to include the body of file3b.h at most once for declarations, and at most once for definitions, but you might need both in a single translation unit (TU — a combination of a source file and the headers it uses).

Multiple inclusion with variable definitions

However, it can be done subject to a not too unreasonable constraint. Let's introduce a new set of file names:

  • external.h for the EXTERN macro definitions, etc.
  • file1c.h to define types (notably, struct oddball, the type of oddball_struct).
  • file2c.h to define or declare the global variables.
  • file3c.c which defines the global variables.
  • file4c.c which simply uses the global variables.
  • file5c.c which shows that you can declare and then define the global variables.
  • file6c.c which shows that you can define and then (attempt to) declare the global variables.

In these examples, file5c.c and file6c.c directly include the header file2c.h several times, but that is the simplest way to show that the mechanism works. It means that if the header was indirectly included twice, it would also be safe.

The restrictions for this to work are:

  1. The header defining or declaring the global variables may not itself define any types.
  2. Immediately before you include a header that should define variables, you define the macro DEFINE_VARIABLES.
  3. The header defining or declaring the variables has stylized contents.

external.h

/*
** This header must not contain header guards (like <assert.h> must not).
** Each time it is invoked, it redefines the macros EXTERN, INITIALIZE
** based on whether macro DEFINE_VARIABLES is currently defined.
*/
#undef EXTERN
#undef INITIALIZE

#ifdef DEFINE_VARIABLES
#define EXTERN              /* nothing */
#define INITIALIZE(...)     = __VA_ARGS__
#else
#define EXTERN              extern
#define INITIALIZE(...)     /* nothing */
#endif /* DEFINE_VARIABLES */
Run Code Online (Sandbox Code Playgroud)

file1c.h

#ifndef FILE1C_H_INCLUDED
#define FILE1C_H_INCLUDED

struct oddball
{
    int a;
    int b;
};

extern void use_them(void);
extern int increment(void);
extern int oddball_value(void);

#endif /* FILE1C_H_INCLUDED */
Run Code Online (Sandbox Code Playgroud)

file2c.h

/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE2C_H_DEFINITIONS)
#undef FILE2C_H_INCLUDED
#endif

#ifndef FILE2C_H_INCLUDED
#define FILE2C_H_INCLUDED

#include "external.h"   /* Support macros EXTERN, INITIALIZE */
#include "file1c.h"     /* Type definition for struct oddball */

#if !defined(DEFINE_VARIABLES) || !defined(FILE2C_H_DEFINITIONS)

/* Global variable declarations / definitions */
EXTERN int global_variable INITIALIZE(37);
EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 });

#endif /* !DEFINE_VARIABLES || !FILE2C_H_DEFINITIONS */

/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE2C_H_DEFINITIONS
#endif /* DEFINE_VARIABLES */

#endif /* FILE2C_H_INCLUDED */
Run Code Online (Sandbox Code Playgroud)

file3c.c

#define DEFINE_VARIABLES
#include "file2c.h"  /* Variables now defined and initialized */

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
Run Code Online (Sandbox Code Playgroud)

file4c.c

#include "file2c.h"
#include <stdio.h>

void use_them(void)
{
    printf("Global variable: %d\n", global_variable++);
    oddball_struct.a += global_variable;
    oddball_struct.b -= global_variable / 2;
}
Run Code Online (Sandbox Code Playgroud)

file5c.c

#include "file2c.h"     /* Declare variables */

#define DEFINE_VARIABLES
#include "file2c.h"  /* Variables now defined and initialized */

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
Run Code Online (Sandbox Code Playgroud)

file6c.c

#define DEFINE_VARIABLES
#include "file2c.h"     /* Variables now defined and initialized */

#include "file2c.h"     /* Declare variables */

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
Run Code Online (Sandbox Code Playgroud)

The next source file completes the source (provides a main program) for prog5, prog6 and prog7:

prog5.c

#include "file2c.h"
#include <stdio.h>

int main(void)
{
    use_them();
    global_variable += 19;
    use_them();
    printf("Increment: %d\n", increment());
    printf("Oddball:   %d\n", oddball_value());
    return 0;
}
Run Code Online (Sandbox Code Playgroud)
  • prog5 uses prog5.c, file3c.c, file4c.c, file1c.h, file2c.h, external.h.
  • prog6 uses prog5.c, file5c.c, file4c.c, file1c.h, file2c.h, external.h.
  • prog7 uses prog5.c, file6c.c, file4c.c, file1c.h, file2c.h, external.h.

This scheme avoids most problems. You only run into a problem if a header that defines variables (such as file2c.h) is included by another header (say file7c.h) that defines variables. There isn't an easy way around that other than "don't do it".

You can partially work around the problem by revising file2c.h into file2d.h:

file2d.h

/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE2D_H_DEFINITIONS)
#undef FILE2D_H_INCLUDED
#endif

#ifndef FILE2D_H_INCLUDED
#define FILE2D_H_INCLUDED

#include "external.h"   /* Support macros EXTERN, INITIALIZE */
#include "file1c.h"     /* Type definition for struct oddball */

#if !defined(DEFINE_VARIABLES) || !defined(FILE2D_H_DEFINITIONS)

/* Global variable declarations / definitions */
EXTERN int global_variable INITIALIZE(37);
EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 });

#endif /* !DEFINE_VARIABLES || !FILE2D_H_DEFINITIONS */

/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE2D_H_DEFINITIONS
#undef DEFINE_VARIABLES
#endif /* DEFINE_VARIABLES */

#endif /* FILE2D_H_INCLUDED */
Run Code Online (Sandbox Code Playgroud)

The issue becomes 'should the header include #undef DEFINE_VARIABLES?' If you omit that from the header and wrap any defining invocation with #define and #undef:

#define DEFINE_VARIABLES
#include "file2c.h"
#undef DEFINE_VARIABLES
Run Code Online (Sandbox Code Playgroud)

in the source code (so the headers never alter the value of DEFINE_VARIABLES), then you should be clean. It is just a nuisance to have to remember to write the the extra line. An alternative might be:

#define HEADER_DEFINING_VARIABLES "file2c.h"
#include "externdef.h"
Run Code Online (Sandbox Code Playgroud)

externdef.h

/*
** This header must not contain header guards (like <assert.h> must not).
** Each time it is included, the macro HEADER_DEFINING_VARIABLES should
** be defined with the name (in quotes - or possibly angle brackets) of
** the header to be included that defines variables when the macro
** DEFINE_VARIABLES is defined.  See also: external.h (which uses
** DEFINE_VARIABLES and defines macros EXTERN and INITIALIZE
** appropriately).
**
** #define HEADER_DEFINING_VARIABLES "file2c.h"
** #include "externdef.h"
*/

#if defined(HEADER_DEFINING_VARIABLES)
#define DEFINE_VARIABLES
#include HEADER_DEFINING_VARIABLES
#undef DEFINE_VARIABLES
#undef HEADER_DEFINING_VARIABLES
#endif /* HEADER_DEFINING_VARIABLES */
Run Code Online (Sandbox Code Playgroud)

This is getting a tad convoluted, but seems to be secure (using the file2d.h, with no #undef DEFINE_VARIABLES in the file2d.h).

file7c.c

/* Declare variables */
#include "file2d.h"

/* Define variables */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"

/* Declare variables - again */
#include "file2d.h"

/* Define variables - again */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
Run Code Online (Sandbox Code Playgroud)

file8c.h

/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE8C_H_DEFINITIONS)
#undef FILE8C_H_INCLUDED
#endif

#ifndef FILE8C_H_INCLUDED
#define FILE8C_H_INCLUDED

#include "external.h"   /* Support macros EXTERN, INITIALIZE */
#include "file2d.h"     /* struct oddball */

#if !defined(DEFINE_VARIABLES) || !defined(FILE8C_H_DEFINITIONS)

/* Global variable declarations / definitions */
EXTERN struct oddball another INITIALIZE({ 14, 34 });

#endif /* !DEFINE_VARIABLES || !FILE8C_H_DEFINITIONS */

/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE8C_H_DEFINITIONS
#endif /* DEFINE_VARIABLES */

#endif /* FILE8C_H_INCLUDED */
Run Code Online (Sandbox Code Playgroud)

file8c.c

/* Define variables */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"

/* Define variables */
#define HEADER_DEFINING_VARIABLES "file8c.h"
#include "externdef.h"

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
Run Code Online (Sandbox Code Playgroud)

The next two files complete the source for prog8 and prog9:

prog8.c

#include "file2d.h"
#include <stdio.h>

int main(void)
{
    use_them();
    global_variable += 19;
    use_them();
    printf("Increment: %d\n", increment());
    printf("Oddball:   %d\n", oddball_value());
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

file9c.c

#include "file2d.h"
#include <stdio.h>

void use_them(void)
{
    printf("Global variable: %d\n", global_variable++);
    oddball_struct.a += global_variable;
    oddball_struct.b -= global_variable / 2;
}
Run Code Online (Sandbox Code Playgroud)
  • prog8 uses prog8.c, file7c.c, file9c.c.
  • prog9 uses prog8.c, file8c.c, file9c.c.

However, the problems are relatively unlikely to occur in practice, especially if you take the standard advice to

Avoid global variables


Does this exposition miss anything?

Confession: The 'avoiding duplicated code' scheme outlined here was developed because the issue affects some code I work on (but don't own), and is a niggling concern with the scheme outlined in the first part of the answer. However, the original scheme leaves you with just two places to modify to keep variable definitions and declarations synchronized, which is a big step forward over having exernal variable declarations scattered throughout the code base (which really matters when there are thousands of files in total). However, the code in the files with the names fileNc.[ch] (plus external.h and externdef.h) shows that it can be made to work. Clearly, it would not be hard to create a header generator script to give you the standardized template for a variable defining and declaring header file.

NB These are toy programs with just barely enough code to make them marginally interesting. There is repetition within the examples that could be removed, but isn't to simplify the pedagogical explanation. (For example: the difference between prog5.c and prog8.c is the name of one of the headers that are included. It would be possible to reorganize the code so that the main() function was not repeated, but it would conceal more than it revealed.)

  • 如果你停在顶部,它可以保持简单的事情.当你进一步阅读时,它会处理更多的细微差别,复杂性和细节.我刚刚为经验较少的C程序员添加了两个"早期停止点" - 或者已经知道该主题的C程序员.如果您已经知道答案,则无需全部阅读(但如果您发现技术故障,请告诉我). (15认同)
  • @litb:关于通用定义见附件J.5.11 - 它是一个常见的扩展. (3认同)
  • @litb:我同意应该避免 - 这就是为什么它出现在'不太好的方式来定义全局变量'部分. (3认同)
  • 实际上它是一个常见的扩展,但它是一个程序依赖它的未定义行为.我不清楚你是否说这是C自己的规则所允许的.现在我看到你说这只是一个常见的扩展,如果你需要你的代码是可移植的,可以避免它.所以我可以毫无疑问地向你投票.真的很好回答恕我直言:) (3认同)
  • @Zak:不会.`file3a.h`中的条件代码是`#ifdef DEFINE_VARIABLES/#define EXTERN/#else/#define EXTERN extern/#endif`,删除注释并使用斜杠标记行的末尾.如果指定了`DEFINE_VARIABLES`,那么变量不应该有`extern`前缀,这会将它们标记为声明而不是定义.反过来,这意味着编译器将为变量分配空间,而不是简单地记录它们的存在. (3认同)
  • @supercat:我发现你可以使用C99数组文字来获取数组大小的枚举值,例如(`foo.h`):`#define FOO_INITIALIZER {1,2,3,4,5}`定义数组的初始值设定项,`enum {FOO_SIZE = sizeof((int [])FOO_INITIALIZER)/ sizeof(((int [])FOO_INITIALIZER)[0])};`得到数组的大小,并且` extern int foo [];`声明数组.显然,定义应该只是`int foo [FOO_SIZE] = FOO_INITIALIZER;`,尽管大小并不一定要包含在定义中.这会得到一个整数常量,`FOO_SIZE`. (3认同)
  • 这个答案是我长期以来读过的最好的书之一! (3认同)
  • 对于多个模块中的代码需要使用相同的初始化数组*并知道其大小*的场景,是否有任何合理的模式?至少有些编译器会认为 `extern int foo[] = {1,2,3};` 等同于 `extern int foo[3];`,因此可以使用一些预处理器逻辑来选择性地省略“extern”当文件包含在其主源文件中时。不过,我还没有想出任何办法让它看起来不那么难看。 (2认同)
  • @JonathanLeffler:对于嵌入式系统,常量和从未写过的变量之间的差异可能很大。C 从未定义过除地址之外的链接时间常量的定义方法,这真的太糟糕了,因为我认为即使在设计 C 时链接器系统也可以支持这样的概念,至少对于不大于 `int` 的事物。 (2认同)
  • 回到学习C/Objective-C/C++时,这是一个很好的答案,但是应该更早地注意标题保护.原因是,如果你对任何*nix做任何事情,这是不可避免的标准做法. (2认同)
  • 为什么在声明函数时使用extern? (2认同)
  • @Shubashree:嗯 - 很有意思.好; 我添加了`prog1.mk`来显示'minimal'makefile.当然,它并非完全微乎其微.这允许我在必要时调整构建,但覆盖了大多数基础,留下了一组非常严格的编译选项.并不是说使用的某些`xFLAGS`名称在标准Make中有其他用途(特别是`GFLAGS` - 与SCCS有关,但你可能不使用SCCS,所以它可能并不重要).谨防! (2认同)

Joh*_*iss 123

extern变量是在另一个翻译单元中定义的变量的声明(由于sbi用于校正).这意味着变量的存储空间分配在另一个文件中.

假设你有两个.c-files test1.ctest2.c.如果您在其中定义了一个全局变量int test1_var;,test1.c并且您想要访问此变量,test2.c则必须使用extern int test1_var;in test2.c.

完整样本:

$ cat test1.c 
int test1_var = 5;
$ cat test2.c
#include <stdio.h>

extern int test1_var;

int main(void) {
    printf("test1_var = %d\n", test1_var);
    return 0;
}
$ gcc test1.c test2.c -o test
$ ./test
test1_var = 5
Run Code Online (Sandbox Code Playgroud)

  • 没有"伪定义".这是一个宣言. (20认同)
  • 在上面的例子中,如果我将 `extern int test1_var;` 更改为 `int test1_var;`,链接器(gcc 5.4.0)仍然通过。那么,在这种情况下真的需要 `extern` 吗? (3认同)
  • @radiohead:在我的[回答](/sf/answers/100337121/)中,你会发现丢弃`extern`的信息是一个常用的常用扩展 - 并且特别适用于GCC(但是GCC远不是唯一支持它的编译器;它在Unix系统上很普遍).您可以在我的答案中查找"J.5.11"或"不太好的方式"部分(我知道 - 它_is_长),附近的文字解释了它(或尝试这样做). (2认同)

Ark*_*nez 40

Extern是用于声明变量本身位于另一个转换单元中的关键字.

因此,您可以决定在翻译单元中使用变量,然后从另一个变量访问它,然后在第二个变量中将其声明为extern,并且链接器将解析该符号.

如果你没有将它声明为extern,你将获得两个名为相同但根本不相关的变量,以及变量的多个定义的错误.

  • 换句话说,使用extern的翻译单元知道这个变量,它的类型等,因此允许底层逻辑中的源代码使用它,但它不会_allocate_变量,另一个翻译单元会这样做.如果两个转换单元都要正常声明变量,那么变量将有效地存在两个物理位置,在编译的代码中具有相关的"错误"引用,并且导致链接器产生歧义. (5认同)

Bug*_*boy 26

我喜欢将extern变量视为您对编译器的承诺.

遇到extern时,编译器只能找到它的类型,而不是它"存在"的位置,因此它无法解析引用.

你告诉它,"相信我.在链接时,这个引用将是可解析的."


Ben*_*enB 18

extern告诉编译器要相信这个变量的内存是在别处声明的,所以它不会尝试分配/检查内存.

因此,您可以编译引用extern的文件,但如果未在某处声明该内存,则无法链接.

对全局变量和库很有用,但是很危险,因为链接器没有键入check.


sbi*_*sbi 15

添加extern将变量定义转换为变量声明.请参阅此主题,了解声明和定义之间的区别.


Ale*_*ood 11

对extern的正确解释是你告诉编译器一些东西.您告诉编译器,尽管现在不存在,但是声明的变量将以某种方式由链接器找到(通常在另一个对象(文件)中).无论你是否有一些外部声明,链接器将是幸运的人找到所有东西并把它放在一起.


Luc*_*Nut 11

                 declare | define   | initialize |
                ----------------------------------

extern int a;    yes          no           no
-------------
int a = 2019;    yes          yes          yes
-------------
int a;           yes          yes          no
-------------
Run Code Online (Sandbox Code Playgroud)

声明不会分配内存(必须为内存分配定义变量),但是定义会。这只是对extern关键字的另一种简单见解,因为其他答案确实很棒。


Pho*_*225 8

在C中,文件中的变量表示example.c给出了局部范围.编译器期望变量的定义在同一个文件example.c中,当它找不到相同的时候,它会抛出一个错误.另一方面,一个函数默认具有全局范围.因此,您不必明确地向编译器提及"看起来很粗鲁......您可能会在这里找到此函数的定义".对于包含包含其声明的文件的函数就足够了.(实际上称为头文件的文件).例如,考虑以下2个文件:
example.c

#include<stdio.h>
extern int a;
main(){
       printf("The value of a is <%d>\n",a);
}
Run Code Online (Sandbox Code Playgroud)

example1.c

int a = 5;
Run Code Online (Sandbox Code Playgroud)

现在,当您将两个文件一起编译时,请使用以下命令:

步骤1)cc -o ex example.c example1.c step 2)./ ex

您将获得以下输出:a的值为<5>


Anu*_*nup 7

extern关键字与变量一起用于将其标识为全局变量.

它还表示您可以在任何文件中使用使用extern关键字声明的变量,尽管它在其他文件中声明/定义.


Cir*_*四事件 6

GCC ELF Linux实现

main.c:

#include <stdio.h>

int not_extern_int = 1;
extern int extern_int;

void main() {
    printf("%d\n", not_extern_int);
    printf("%d\n", extern_int);
}
Run Code Online (Sandbox Code Playgroud)

编译和反编译:

gcc -c main.c
readelf -s main.o
Run Code Online (Sandbox Code Playgroud)

输出包含:

Num:    Value          Size Type    Bind   Vis      Ndx Name
 9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 not_extern_int
12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND extern_int
Run Code Online (Sandbox Code Playgroud)

系统V ABI更新ELF规范 "符号表"一章解释说:

SHN_UNDEF此节表索引表示符号未定义.当链接编辑器将此目标文件与定义指示符号的另一个目标文件组合在一起时,该文件对该符号的引用将链接到实际定义.

这基本上是C标准给extern变量的行为.

从现在开始,链接器的工作就是制作最终的程序,但是extern信息已经从源代码中提取到了目标文件中.

在GCC 4.8上测试过.

C++ 17内联变量

在C++ 17中,您可能希望使用内联变量而不是外部变量,因为它们易于使用(可以在标题上定义一次)并且功能更强大(支持constexpr).请参阅:"const static"在C和C++中的含义是什么?

  • 这不是我的投票,所以我不知道.但是,我会提出意见.虽然查看`readelf`或`nm`的输出可能会有所帮助,但是你没有解释如何使用`extern`的基本原理,也没有用实际的定义完成第一个程序.你的代码甚至不使用`notExtern`.还有一个命名问题:虽然`notExtern`在这里被定义而不是用`extern`声明,但它是一个外部变量,如果那些翻译单元包含一个合适的声明(需要`extern int),它可以被其他源文件访问. notExtern;`)! (3认同)

log*_*hee 5

extern 允许程序的一个模块访问程序的另一个模块中声明的全局变量或函数.您通常在头文件中声明了外部变量.

如果您不希望程序访问您的变量或函数,则使用static它告诉编译器此变量或函数不能在此模块之外使用.


Ger*_*mia 5

extern 简单地表示变量在别处定义(例如,在另一个文件中).