C 语言的 static 关键字作用

C 语言里的 static 不只有"限制符号的链接性,只在本编译单元可见"这一种功能。

更准确地说,static 在 C 里根据出现位置不同,主要有几类含义:

  1. 文件作用域的变量/函数:赋予内部链接性

    也就是"只在当前翻译单元可见"。

  2. 块作用域的局部变量:改变存储期为静态存储期

    变量只初始化一次,生命周期贯穿整个程序运行期间,但名字仍然只在当前块内可见。

  3. 函数参数中的数组声明:表示调用者传入的数组至少有指定长度

    这是 C99 引入的特性,例如 int a[static 10]

  4. inline 组合时,C99/C11/C17/C23 与 GNU89 语义有差异

    这是 GNU C 扩展/不同标准模式下一个很容易踩坑的地方。

下面逐个说明。


1. 文件作用域下的 static:内部链接性

这是你提到的那种情况。

例如:

c 复制代码
// a.c

static int g = 10;

static void foo(void)
{
    printf("foo\n");
}

这里:

c 复制代码
static int g;
static void foo(void);

表示 gfoo 具有 internal linkage,内部链接性

也就是说,它们只能在 a.c 这个翻译单元中使用,别的 .c 文件无法通过 extern 访问它们。

例如:

c 复制代码
// a.c
static int g = 10;

int get_g(void)
{
    return g;
}
c 复制代码
// b.c
extern int g;

int main(void)
{
    return g;
}

编译链接时通常会报错:

text 复制代码
undefined reference to `g`

因为 a.c 里的 gstatic 的,对外不可见。

所以在文件作用域下,static 的确常常用于:

c 复制代码
static int global_var;
static void helper_function(void);

目的是隐藏符号,避免污染全局命名空间。


2. 块作用域下的 static:静态存储期,而不是内部链接性

例如:

c 复制代码
#include <stdio.h>

void counter(void)
{
    static int x = 0;
    x++;
    printf("%d\n", x);
}

int main(void)
{
    counter();
    counter();
    counter();
}

输出:

text 复制代码
1
2
3

这里的:

c 复制代码
static int x = 0;

不是说 x 具有"内部链接性"。事实上,块作用域里的局部变量没有链接性。

它的作用是:

  • x 的生命周期从程序开始持续到程序结束;
  • 但名字 x 只能在 counter 函数内部访问;
  • 初始化只发生一次;
  • 多次调用 counter 时,x 保留上次的值。

对比普通局部变量:

c 复制代码
void counter(void)
{
    int x = 0;
    x++;
    printf("%d\n", x);
}

每次调用都会输出:

text 复制代码
1
1
1

所以块作用域下的 static 主要改变的是 存储期 storage duration,不是链接性。


3. 函数参数数组中的 static:至少需要多少元素

这是 C99 引入的功能。

例如:

c 复制代码
void f(int a[static 10])
{
    for (int i = 0; i < 10; i++) {
        a[i] = i;
    }
}

这里:

c 复制代码
int a[static 10]

表示调用者传进来的 a 至少指向 10 个 int 元素。

它和下面这个声明在类型层面非常接近:

c 复制代码
void f(int *a);

static 10 给编译器额外信息:

c 复制代码
void f(int a[static 10]);

相当于告诉编译器:

我保证 a 不是空指针,并且至少有 10 个元素可访问。

例如:

c 复制代码
int arr[10];
f(arr);    // OK

但是:

c 复制代码
int arr[5];
f(arr);    // 违反约定,行为未定义或至少不符合函数要求

再比如:

c 复制代码
f(NULL);   // 不符合 static 10 的要求

一些编译器可能会给警告。

完整例子:

c 复制代码
#include <stdio.h>

void fill(int a[static 5])
{
    for (int i = 0; i < 5; i++) {
        a[i] = i + 1;
    }
}

int main(void)
{
    int x[5];
    fill(x);

    for (int i = 0; i < 5; i++) {
        printf("%d\n", x[i]);
    }
}

输出:

text 复制代码
1
2
3
4
5

这种用法只允许出现在函数参数的数组声明中,例如:

c 复制代码
void f(int a[static 10]);
void g(double matrix[static 3][5]);

注意,下面这种不是同一种意思:

c 复制代码
static int a[10];

这是静态存储期或者内部链接性的变量声明,取决于作用域。


4. staticinline 的组合:标准 C 和 GNU C 有差异

这部分是最容易体现"不是所有版本都一样"的地方。

例如头文件里常见:

c 复制代码
// util.h
static inline int add(int a, int b)
{
    return a + b;
}

这通常表示:

  • 每个包含这个头文件的 .c 文件都拥有自己的一份 add
  • 函数具有内部链接性;
  • 编译器可以选择内联,也可以不内联;
  • 不会产生多个外部定义冲突。

因此 static inline 是头文件中定义小函数的常见写法。

例如:

c 复制代码
// util.h
#ifndef UTIL_H
#define UTIL_H

static inline int square(int x)
{
    return x * x;
}

#endif
c 复制代码
// a.c
#include "util.h"

int fa(int x)
{
    return square(x);
}
c 复制代码
// b.c
#include "util.h"

int fb(int x)
{
    return square(x);
}

这是安全的,因为 square 在每个翻译单元中都是内部链接。


但是 inline 本身在 GNU89 和 C99 语义不同

例如:

c 复制代码
inline int foo(int x)
{
    return x + 1;
}

在 C99/C11/C17/C23 标准语义和 GNU89 语义下,外部定义生成规则不同。

简单说:

写法 C99 语义 GNU89 语义
inline int f() 通常不提供外部定义 通常会生成外部定义
extern inline int f() 通常提供外部定义 通常不生成外部定义
static inline int f() 内部链接,较稳定 内部链接,较稳定

因此在跨 C 标准和 GNU C 模式时,static inline 通常最不容易出问题。

例如 GCC 有这些模式:

bash 复制代码
gcc -std=c89
gcc -std=c99
gcc -std=c11
gcc -std=c17
gcc -std=c2x
gcc -std=gnu89
gcc -std=gnu99
gcc -std=gnu11
gcc -std=gnu17
gcc -std=gnu2x

gnu89 模式和 c99/gnu99 之后,inline 的语义可能不同。

不过 static inline 的直观效果通常仍然是:

c 复制代码
static inline int f(int x)
{
    return x + 1;
}

即:

  • f 具有内部链接性;
  • 每个翻译单元可有自己的 f
  • 编译器可以内联,也可以不内联。

5. 不同 C 标准中 static 功能是否完全一样?

不完全一样。

大致如下:

C 版本 static 的主要功能
C89/C90 文件作用域内部链接性;块作用域静态存储期
C99 增加函数参数数组中的 static;引入标准 inline,和 static inline 配合使用
C11 基本继承 C99;增加 _Thread_local,可与 static 组合
C17/C18 基本继承 C11
C23 基本继承上述语义,语法细节有所现代化
GNU C 支持标准功能,另有 GNU inline 语义等扩展差异

6. C11 之后:static_Thread_local

C11 引入了线程局部存储:

c 复制代码
_Thread_local int x;

可以和 static 组合。

例如文件作用域:

c 复制代码
static _Thread_local int counter;

表示:

  • counter 具有内部链接性;
  • 每个线程都有自己的一份 counter

例如:

c 复制代码
static _Thread_local int tls_value = 0;

这和普通:

c 复制代码
static int value = 0;

不同。

普通 static int value 是整个程序共享一个对象。

而:

c 复制代码
static _Thread_local int tls_value;

是每个线程一份对象,并且符号只在本翻译单元可见。


7. 几个对比例子

例 1:文件作用域变量

c 复制代码
static int x = 1;

如果写在函数外:

c 复制代码
// file scope
static int x = 1;

含义:

  • 静态存储期;
  • 内部链接性;
  • 只能在当前 .c 文件中通过名字访问。

例 2:函数内局部变量

c 复制代码
void f(void)
{
    static int x = 1;
}

含义:

  • 静态存储期;
  • 无链接性;
  • 名字 x 只在 f 内可见;
  • 不是"只在本编译单元可见"的问题,而是只在这个块里可见。

例 3:函数声明

c 复制代码
static void helper(void);

如果在文件作用域:

c 复制代码
static void helper(void)
{
}

含义:

  • helper 具有内部链接性;
  • 只能在当前翻译单元调用;
  • 常用于 .c 文件内部的辅助函数。

例 4:函数参数数组

c 复制代码
void sort(int a[static 100])
{
    // 可以假设 a 至少有 100 个 int
}

含义:

  • a 仍然会调整为指针参数;
  • 但调用者必须保证至少有 100 个元素;
  • 这是 C99 之后才有的语义。

例 5:头文件中的 static inline

c 复制代码
// math_util.h
static inline int max_int(int a, int b)
{
    return a > b ? a : b;
}

含义:

  • 每个包含此头文件的 .c 文件都有自己的 max_int
  • 不会造成多个定义的链接错误;
  • 编译器可以内联。

总结

你的说法:

static 的功能是"限制符号的链接性,只在本编译单元可见"

只对一部分情况成立,主要是:

c 复制代码
static int global_var;
static void function(void);

也就是 文件作用域 下的变量和函数。

static 还有其他功能:

c 复制代码
void f(void)
{
    static int x;
}

这里表示 静态存储期,不是链接性。

c 复制代码
void g(int a[static 10]);

这里表示函数参数 a 至少指向 10 个元素,这是 C99 引入的数组参数约束。

所以不能简单地说 C 语言中的 static 就是"限制链接性"。

更准确的总结是:

static 是一个存储类说明符。在文件作用域下,它通常赋予标识符内部链接性;在块作用域下,它让对象具有静态存储期;在函数参数数组声明中,它表示实参数组至少具有指定长度。不同 C 标准和 GNU C 模式下,特别是与 inline 组合时,语义存在差异。

相关推荐
七颗糖很甜7 小时前
基于IRI-2016模型计算电子密度、TEC、foF2等参数的技术原理与代码实现
大数据·python·算法
枫叶丹47 小时前
【HarmonyOS 6.0】Camera Kit白平衡API深度解析:让三方应用真正“掌控”色彩
开发语言·华为·harmonyos·视频编解码
xyq20247 小时前
C# 运算符重载
开发语言
echome8887 小时前
Python 生成器与 yield 关键字实战:5 个节省内存的高级用法与性能优化技巧
开发语言·python
风筝在晴天搁浅7 小时前
LeetCode 143.重排链表
算法·leetcode·链表
码界筑梦坊7 小时前
112-基于Flask的游戏行业销售数据可视化分析系统
开发语言·python·游戏·信息可视化·flask·毕业设计·echarts
碧海银沙音频科技研究院7 小时前
如何彻底关闭360壁纸
人工智能·深度学习·算法
sali-tec7 小时前
C# 基于OpenCv的视觉工作流-章57-人脸识别
图像处理·人工智能·opencv·算法·计算机视觉
计算机安禾7 小时前
【Linux从入门到精通】第43篇:I/O调度算法与磁盘性能优化
linux·算法·性能优化