9. static 关键字

static 关键字的作用是在函数定义时修饰并限定一个标识符(函数名或变量名)它的作用域是模块内(静态全局变量或函数)或函数内(静态局部变量)的静态全局变量,而非程序内全局或局部变量。

static 关键字修饰的标识符的类型:

  1. 静态全局变量和函数。
  2. 静态局部变量。

1、静态全局变量和函数

要理解 static 关键字。我们先从静态全局变量和函数讲起。在多文件编译时,所有的全局变量和函数都是程序内全局,而非模块内全局。就是说如果再同一个程序中的不同模块内定义两个相同名称的全局标识符,可能会引起冲突问题。下面我们用示例来讲解。

我们现在写一个程序 static_demo ,此程序由三个文件:a.cb.cmain.c三个文件组成。其内容如下:

文件:a.c 的内容如下:

// filename: a.c
#include <stdio.h>

int gx = 100;

int fx(void)
{
    printf("fx(void) 函数被调用!\n");
}

文件:b.c 的内容如下:

// filename: b.c
#include <stdio.h>

int gx = 666;

int fx(int x)
{
    printf("fx(int x) 函数被调用!\n");
}

文件:main.c 的内容如下:

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

int main(int argc, char * argv[]) {
    extern int gx;
    extern int fx(void);

    printf("gx: %d\n", gx);

    fx();
    return 0;
}

从上述程序中,我们发现文件 a.c 和文件 b.c 内部有同名的标识符 gxfx,那在 main 函数内调用的 gx 变量和函数 fx 是哪一个呢?让我们编译一下来看看。

weimingze@mzstudio:~/static_demo$ gcc -c -o main.o main.c -I.
weimingze@mzstudio:~/static_demo$ gcc -c -o a.o a.c -I.
weimingze@mzstudio:~/static_demo$ gcc -c -o b.o b.c -I.
weimingze@mzstudio:~/static_demo$ gcc -o static_demo main.o a.o b.o
/usr/bin/ld: b.o:(.data+0x0): multiple definition of `gx'; a.o:(.data+0x0): first defined here
/usr/bin/ld: b.o: in function `fx':
b.c:(.text+0x0): multiple definition of `fx'; a.o:a.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status

从上述 GCC 编译过程可知,在汇编生成目标文件 a.ob.omain.o 的过程都是没有问题的。但在链接阶段要生成 static_demo 时报告重复定义 gxfx,最终编译失败。

错误的原因是程序由三个模块main.ca.cb.c 组成。而 a.c 这三个模块中有两个 gxfx 全局标识符,致使 main 函数无法决定调用哪一个 gx 变量和 fx 函数而报错,最终将由开发人员改写代码手动解决。

假设 a.c 内的 gx 变量只供 a.c 文件内的函数使用,那我们可以限定 a.c 内的 gx 变量在生成为目标文件 a.o 时不导出其标识符 gx,那么在定义这个全局变量时可以在类型前加上 static 关键字修饰,如: static int gx = 100;static 修饰的标识符仅模块内可见,因此 main 函数将只能看到 b.c 内的 gx,从而解决了 gx 重复定义的问题。

同理,我们假设 b.c 文件内的函数 int fx(int x) 也仅供 b.c 文件内的其它函数调用,那我们也将此函数加上 static 关键字修饰。这样就只有 a.c 文件内的 int fx(void) 函数全局可见了。

我们改写上述程序中的 a.cb.c 如下:

文件:a.c 的内容修改如下:

// filename: a.c
#include <stdio.h>

static int gx = 100;  // 声明为模块内全局变量

int fx(void)
{
    printf("fx(void) 函数被调用!\n");
}

文件:b.c 的内容修改如下:

// filename: b.c
#include <stdio.h>

int gx = 666;

static int fx(int x)  // 声明为模块内函数。
{
    printf("fx(int x) 函数被调用!\n");
}

再次重新编译运行如下:

weimingze@mzstudio:~/static_demo$ gcc -c -o main.o main.c -I.
weimingze@mzstudio:~/static_demo$ gcc -c -o a.o a.c -I.
weimingze@mzstudio:~/static_demo$ gcc -c -o b.o b.c -I.
weimingze@mzstudio:~/static_demo$ gcc -o static_demo main.o a.o b.o
weimingze@mzstudio:~/static_demo$ ./static_demo
gx: 666
fx(void) 函数被调用!

可见编译正确,并能够正确运行。

注意:

需要注意的是 使用 static 修饰的变量或函数,不能在使用 extern 关键字进行声明为外部函数。

2、静态局部变量

在 C 语言中,可以使用 static 关键字来修饰局部变量。被 static 修饰的局部变量称为静态局部变量

说明:

  1. 静态局部变量在定义时必须被初始化。
  2. 静态局部变量 是在数据段定义,而非在栈上定义。即可以理解成是全局变量,但只能在定义函数内部可见。

示例:

定义一个函数 int myadd(int x, int y) 每次调用,将返回 x + y 的和,并打印这是第几次调用。

// filename: static_local_var.c
#include <stdio.h>

int myadd(int x, int y)
{
    static int call_times = 0;
    call_times++;
    printf("这是第 %d 次调用 myadd函数!\n", call_times);
    return x + y;
}

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

    return 0;
}

编译和运行结果如下:

weimingze@mzstudio:~$ gcc -o static_local_var static_local_var.c
weimingze@mzstudio:~$ ./static_local_var
这是第 1 次调用 myadd函数!
300
这是第 2 次调用 myadd函数!
700

从运行结果可见,变量 call_times 并不会在 myadd 函数调用后销毁和重新初始化。你把静态局部变量理解成 仅在定义函数内可以使用的静态全局变量就对了。

实验:

独立写代码来完成 静态全局变量和函数静态局部变量 的上述示例,并通过改写来验证上述讲解是否正确。