8. extern 关键字

extern 关键字主要用于多文件编译中声明变量名或函数名不是在当前模块(.c文件)内定义,而是在其它模块(.c文件)中定义。

extern 关键字的作用 是声明这些标识符(变量名或函数名)在汇编阶段让编译器先预留内存位置,不进行定位,而在链接阶段再根据外部的其它目标文件中这些标识符的定义来定位这些标识符存在的实际位置。

extern 关键字用于两种声明中:

  1. 全局变量声明
  2. 函数声明

先来说以一下定义(Definition)声明(Declaration)的区别。

定义是为变量或函数分配内存空间,在运行时变量或函数一定存在于内存中。

声明时在编译阶段告诉编译器,一个标识符的类型是什么,编译器将根据声明的类型来决定如何处理这个标识符。

变量声明

我在模块文件 a.c 中的函数外写入 int x; 来定义一个全局变量 x, 这时编译器一定会在数据段为变量 x 预留 4 个字节的内存空间以便后续保存数据。当我们在另外一个模块文件 b.c 中 使用 a.c中的全局变量 x 我们应当怎么办呢?因为 a.cb.c 只有在链接阶段才能相遇。因此在编译和汇编 b.c 文件时我们先要告诉编译器 b.c 内的 x 实际是外部模块文件内的整型变量 x,这时我们要 b.c 文件内添加变量声明 extern int x;。这样编译才能准确的识别标识符 x 并为其重新定位地址做好准备。

全局变量定义的语法:

数据类型 变量名1[=初始值1],变量名2[=初始值2],...;

全局变量声明的语法:

extern 数据类型 变量名1,变量名2,...;

说明:

  1. 全局变量定义前一定不能添加 extern 关键字。
  2. 全局变量声明必须在前面添加 extern 关键字。
  3. 局部变量不能用 extern 进行声明,因为局部变量只能存在于函数调用时。
  4. 全局变量声明不能给初始值。

函数声明

在标准 C 语言中函数都是全局的函数,没有局部函数的写法。

函数定义的语法:

数据类型 函数名(数据类型1 形参变量1, 数据类型2 形参变量2, ...) { ... }

函数声明的语法:

[extern] 数据类型 函数名(数据类型1[ 形参变量1], 数据类型2[ 形参变量2], ...);

说明:

  1. 函数定义的形参列表的右小括号后一定要跟一个大括号({}),内部用来写函数的实现。
  2. 函数定义后面不用分号(;)结尾。
  3. 函数声明的形参列表的右小括号后一定要跟一个分号(;)。
  4. 函数声明前面的 extern 关键字可以省略不写,也就是说函数声明自带 extern 属性。
  5. 函数声明的形式参数列表中的形参变量名可以省略不写。
  6. 函数声明的形式参数列表中如果没有形式参数,建议写入 void 类型(C99标准)以便编译器做出准确判断。

示例:

改写上节课的 math_proj 的示例,我们在 mymath.c 内加入整数全局变量 total_call_times 用来记录 myaddmymul 两个函数调用的总次数。在 main 函数结束前我们来打印这两个函数的调用次数。

mymath.c 文件内容修改如下:

// filename: mymath.c
#include "mymath.h"

// 定义全局变量 total_call_times 用来记录 以下两个函数调用的次数
int total_call_times = 0;

int myadd(int x, int y) {
    total_call_times++;  // 每次调用后全局变量个数加1
    return x + y;
}

int mymul(int x, int y) {
    total_call_times++;  // 每次调用后全局变量个数加1
    return x * y;
}

mymath.h 文件内容修改如下:

// filename: mymath.h
#ifndef __MYMATH_H
#define __MYMATH_H

// 变量声明,使用者只需要包含头文件即可,不同在重复声明
extern int total_call_times;

// 函数声明,这次我们加了 extern 关键字
extern int myadd(int x, int y);
extern int mymul(int x, int y);

#endif

main.c 文件内容修改如下:

// filename: main.c
#include <stdio.h>
#include "mymath.h"

int main(int argc, char * argv[]) {
    printf("%d\n", myadd(100, 200));  // 300
    printf("%d\n", mymul(300, 400));  // 120000

    printf("您共调用了 %d 次 mymath.c 内的函数\n", total_call_times);
    return 0;
}

程序的编译和运行结果如下:

weimingze@mzstudio:~/myproject2$ gcc -c -o mymath.o mymath.c -I.
weimingze@mzstudio:~/myproject2$ gcc -c -o main.o main.c -I.
weimingze@mzstudio:~/myproject2$ gcc -o math_proj mymath.o main.o
weimingze@mzstudio:~/myproject2$ ./math_proj
300
120000
您共调用了 2  mymath.c 内的函数

实验:

将你自己写过的程序,以功能为单位拆分成多个 .c 文件,然后进行编译和运行。