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

C 如何使用extern在源文件之间共享变量?,c,global-variables,extern,C,Global Variables,Extern,我知道C中的全局变量有时有extern关键字。什么是extern变量?宣言是什么样的?它的范围是什么 这与跨源文件共享变量有关,但这究竟是如何工作的呢?在哪里使用extern?extern是用于声明变量本身位于另一个转换单元中的关键字 因此,您可以决定在转换单元中使用一个变量,然后从另一个单元访问它,然后在第二个单元中声明它为extern,符号将由链接器解析 如果您不将其声明为extern,您将得到两个名称相同但完全不相关的变量,并且该变量的多个定义存在错误。一个extern变量是另一个翻译单元

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


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

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

因此,您可以决定在转换单元中使用一个变量,然后从另一个单元访问它,然后在第二个单元中声明它为extern,符号将由链接器解析


如果您不将其声明为extern,您将得到两个名称相同但完全不相关的变量,并且该变量的多个定义存在错误。

一个
extern
变量是另一个翻译单元中定义的变量的声明(感谢sbi的更正)。这意味着变量的存储被分配到另一个文件中

假设您有两个
.c
-文件
test1.c
test2.c
。如果您定义了一个全局变量
inttest1\u vartest2.c
中访问此变量,必须使用
extern int test1\u var
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
$cat test1.c
int test1_var=5;
$cat test2.c
#包括
外部内部测试1_var;
内部主(空){
printf(“test1\u-var=%d\n”,test1\u-var);
返回0;
}
$gcc test1.c test2.c-o测试
美元/测试
test1_var=5

添加
extern
将变量定义转换为变量声明。看看声明和定义之间的区别

extern告诉编译器相信您该变量的内存是在别处声明的,因此它不会尝试分配/检查内存

因此,您可以编译一个引用外部的文件,但如果内存没有在某处声明,则无法链接


对全局变量和库有用,但危险是因为链接器不进行类型检查。

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

重要的是:

  • 当编译器被告知 变量存在(这是它的类型);它不分配资源 此时变量的存储

  • 当编译器为变量分配存储时,定义了一个变量 变量

可以多次声明变量(尽管一次就足够了); 在给定范围内只能定义一次。 变量定义也是一种声明,但不是所有变量 声明是定义

声明和定义全局变量的最佳方法 声明和定义全局变量的干净、可靠的方法是使用 包含变量的
外部声明的头文件

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

尽管有其他方法可以做到这一点,但这种方法简单易行 可靠。 它可以通过
file3.h
file1.c
file2.c
进行演示:

文件3.h
extern int全局变量;/*变量的声明*/
文件1.c
#包括此处提供的“file3.h”/*声明*/
#包括“prog1.h”/*函数声明*/
/*此处定义的变量*/
int全局_变量=37;/*根据声明检查定义*/
int增量(void){return global_variable++;}
文件2.c
#包括“file3.h”
#包括“prog1.h”
#包括
作废使用它(作废)
{
printf(“全局变量:%d\n”,全局变量++);
}
这是声明和定义全局变量的最佳方法


接下来的两个文件完成了
prog1
的源代码:

显示的完整程序使用函数,因此函数声明 潜入。 C99和C11都需要先声明或定义函数,然后才能使用它们 使用了(而C90没有使用,原因很好)。 我在标题中的函数声明前面使用关键字
extern
一致性-匹配变量前面的
extern
标题中的声明。 许多人不喜欢在函数前面使用
extern
声明;编译器不在乎——最终,我也不在乎 只要是一致的,至少在源文件中是一致的

项目1.h
extern void使用它(void);
外部内部增量(无效);
项目1.c
#包括“file3.h”
#包括“prog1.h”
#包括
内部主(空)
{
使用它();
全局_变量+=19;
使用它();
printf(“增量:%d\n”,增量());
返回0;
}
  • prog1
    使用
    prog1.c
    file1.c
    file2.c
    file3.h
    prog1.h
文件
prog1.mk
仅是
prog1
的生成文件。 它将适用于从大约本回合开始生产的大多数版本的
make
千禧年。 它不是专门与GNU Make绑定的

prog1.mk
指导方针 只有专家才能违反规则,而且必须有充分的理由:

  • 头文件仅包含变量的
    extern
    声明-从不
    静态# 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}
    
    
    #ifndef FILE3B_H_INCLUDED
    #define FILE3B_H_INCLUDED
    
    ...contents of header...
    
    #endif /* FILE3B_H_INCLUDED */
    
    #define DEFINE_VARIABLES
    #include "file2c.h"
    #undef DEFINE_VARIABLES
    
    #define HEADER_DEFINING_VARIABLES "file2c.h"
    #include "externdef.h"
    
    #include<stdio.h>
    extern int a;
    main(){
           printf("The value of a is <%d>\n",a);
    }
    
    int a = 5;
    
    #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);
    }
    
    gcc -c main.c
    readelf -s main.o
    
    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
    
    #ifdef MAIN_C
    #define GLOBAL
     /* #warning COMPILING MAIN.C */
    #else
    #define GLOBAL extern
    #endif
    GLOBAL unsigned char testing_mode; // example var used in several C files
    
    #define MAIN_C 1
    #include "global.h"
    #undef MAIN_C
    
    extern unsigned char testing_mode;
    
                     declare | define   | initialize |
                    ----------------------------------
    
    extern int a;    yes          no           no
    -------------
    int a = 2019;    yes          yes          yes
    -------------
    int a;           yes          yes          no
    -------------
    
    //file foo_globals.h
    #pragma once  
    #include "foo.h"  //contains definition of foo
    
    #ifdef GLOBAL  
    #undef GLOBAL  
    #endif  
    
    #ifdef GLOBAL_FOO_IMPLEMENTATION  
    #define GLOBAL  
    #else  
    #define GLOBAL extern  
    #endif  
    
    GLOBAL Foo foo1;  
    GLOBAL Foo foo2;
    
    
    //file main.cpp
    #define GLOBAL_FOO_IMPLEMENTATION
    #include "foo_globals.h"
    
    //file uses_extern_foo.cpp
    #include "foo_globals.h