C 语言 static 完整讲解

C 语言 static 完整讲解:分三大场景

static 是存储修饰符,作用分全局变量、局部变量、函数三种场景,核心两个能力:

  1. 改变内存存放区域
  2. 限制作用域(文件隔离)

一、场景 1:修饰局部变量(函数内部)

1. 普通局部变量(无 static)

c

运行

复制代码
void func(void)
{
    int a = 0; // 存栈Stack,函数调用时创建,退出直接销毁
    a++;
    printf("%d ", a);
}

调用多次输出:1 1 1 1

  • 内存:栈;每次调用重新初始化为 0;函数结束销毁。

2. static 局部变量

c

运行

复制代码
void func(void)
{
    static int a = 0; // 存.bss/.data段,只初始化1次
    a++;
    printf("%d ", a);
}

调用多次输出:1 2 3 4

核心特性:
  1. 初始化仅执行 1 次:程序上电第一次进函数才赋值,后续调用保留上次值;
  2. 内存不在栈,在全局 RAM (.bss/.data):函数退出不会销毁,生命周期 = 整个程序运行;
  3. 作用域仅限当前函数 :外部其他函数无法访问变量a
  4. 单片机优势:不占用栈空间,递归 / 循环不会栈溢出。

二、场景 2:修饰全局变量(函数外部)

1. 普通全局变量

c

运行

复制代码
int g_val = 10; // 存.data段,整个工程所有.c文件都能extern访问

工程任意文件写 extern int g_val; 就能跨文件读写。

2. static 全局变量

c

运行

复制代码
static int g_val = 10; // 存.data段,仅限当前.c文件使用
核心特性:
  1. 内存区域不变:仍在.data/.bss,程序全程存在;
  2. 文件作用域隔离 :其他.c文件无法通过extern引用,杜绝跨文件重名冲突;
  3. 单片机项目规范:所有模块内部全局缓冲区(NTC 数组、缓存)全部加 static。

三、场景 3:修饰函数

1. 普通函数

c

运行

复制代码
void TestNtc(void)
{
}

整个工程任意文件都能直接调用。

2. static 函数

c

运行

复制代码
static void TestNtc(void)
{
}
核心特性:
  1. 仅限当前.c 文件调用,外部文件无法引用;
  2. 模块化隔离:只给本文件内部使用的工具函数,全部加 static,防止全局函数名泛滥;
  3. 链接时不会导出符号,固件体积轻微减小。

四、static 内存分布汇总表

表格

写法 内存区域 生命周期 作用域
函数内 int a 栈 Stack 函数单次调用 仅当前函数
函数内 static int a .bss/.data 整个程序运行 仅当前函数
文件外 int g_a .bss/.data 整个程序运行 全工程所有文件
文件外 static int g_a .bss/.data 整个程序运行 仅限当前.c 文件
普通函数 void func() 代码段 Flash 程序运行 全工程可调用
static void func() 代码段 Flash 程序运行 仅限当前.c 文件

五、结合 NTC 单片机代码实战例子

例 1:static 局部变量(滤波计数,保留上次值)

c

运行

复制代码
uint16_t Ntc_GetAdc(void)
{
    static uint16_t filter_buf[8]; // 静态数组,不占栈,保存历史采样
    static uint8_t idx = 0;
    uint16_t adc = ADC_Read();
    filter_buf[idx++] = adc;
    idx %= 8;
    // 均值滤波计算...
}

不加 static 的话,filter_buf在栈,数组大了直接栈溢出。

例 2:static 全局表(NTC 码值表,仅 ntc.c 内部使用)

c

运行

复制代码
// ntc.c 文件内,其他文件无法访问这个表
#ifdef NTC_R_10K
static const uint16_t ntc_code[] = {0x01C4,0x023B,...};
#elif defined(NTC_R_100K)
static const uint16_t ntc_code[] = {0xXXXX,...};
#endif

例 3:static 内部工具函数(仅 ntc.c 内部调用)

c

运行

复制代码
// 查表转换温度,外部文件不能调用
static int Ntc_CodeToTemp(uint16_t adc)
{
    // 二分查表逻辑
}

// 对外提供的接口,不加static,其他文件可调用
int Ntc_GetTemp(void)
{
    uint16_t adc = Ntc_ReadAdc();
    return Ntc_CodeToTemp(adc);
}

六、高频易错点

  1. static const 常量:

    • 全局static const:存.rodata只读 Flash,不占 RAM;
    • 函数内static const:同样存在只读区,不会每次进函数重复分配。
  2. static 局部变量初始化只一次: c

    运行

    复制代码
    static int a = 10; // 仅上电执行一次
    a = 10; // 每次调用都会重新赋值,覆盖记忆值
  3. 栈溢出解决方案:大数组、缓冲区全部用static修饰,转移到全局 RAM,不占用栈。

  4. 模块化规范:

    • 模块私有变量 / 工具函数:全部加static
    • 对外接口、跨文件变量:不加 static。

总结

  1. 函数内 static 变量:保值、不占栈、生命周期全局
  2. 全局 static 变量 /static 函数:锁死在当前文件,外部不可访问