对方正在骂你头像
关注

C语言核心关键字详解:static、const、volatile

引言

在C语言中,staticconstvolatile是三个非常重要的关键字。它们虽然简单,但理解不透彻很容易用错。本文将用最简洁的方式,带你彻底搞懂这三个关键字的用法和区别。

一、static关键字

1.1 static的作用

static关键字有三个核心作用:

  • 隐藏:修饰全局变量/函数时,限制作用域在当前文件

  • 持久:修饰局部变量时,延长生命周期到整个程序运行期

  • 默认初始化:静态变量自动初始化为0

1.2 static修饰局部变量

作用:变量在函数退出后值不丢失,下次进入函数时继续使用

#include <stdio.h>

void count() {
    static int num = 0;  // 只初始化一次
    num++;
    printf("%d ", num);
}

int main() {
    count();  // 输出 1
    count();  // 输出 2
    count();  // 输出 3
    return 0;
}

普通局部变量 vs static局部变量

  • 普通局部变量:存储在栈区,函数结束即销毁

  • static局部变量:存储在静态数据区,程序结束才销毁

1.3 static修饰全局变量

作用:变量只能在当前文件中访问,其他文件不可见

// file1.c
static int a = 10;  // 只在file1.c有效

void func1() {
    a = 20;  // 可以访问
}

// file2.c
extern int a;  // 错误!找不到a
void func2() {
    a = 30;  // 编译错误
}

1.4 static修饰函数

作用:函数只能在当前文件中调用,其他文件不可见

// file1.c
static void helper() {  // 私有函数
    printf("helper\n");
}

void public_func() {     // 公开函数
    helper();            // 可以调用
}

// file2.c
extern void public_func();  // 可以
extern void helper();       // 错误!找不到

二、const关键字

2.1 const的作用

const用于定义只读变量,一旦初始化就不能修改。编译器会检查所有试图修改const变量的操作并报错。

2.2 const修饰基本变量

const int a = 10;   // a不能被修改
// a = 20;          // 错误!

int b = 10;
const int* p = &b;  // 指向的内容不能改

2.3 const与指针(两种核心概念)

常量指针与指针常量是两个完全不同的概念,区分它们的关键看const在*的哪一侧:

概念一:常量指针(pointer to const)

定义:指向常量的指针,指针指向的内容不可修改

const int* p;      // const在*左边,指向的内容只读
int const* p;      // 同上,两种写法等价

int a = 10, b = 20;
const int* p = &a;

// *p = 100;       // 错误!不能通过p修改a的值
p = &b;            // 正确!p本身可以指向其他地方
printf("%d", *p);  // 可以读取值
概念二:指针常量(const pointer)

定义:指针本身是常量,一旦指向某个变量就不能再指向其他地方

int* const p = &a;  // const在*右边,指针本身只读

*p = 100;           // 正确!可以通过p修改a的值
// p = &b;          // 错误!p不能再指向其他地方
概念三:双重const

定义:指针本身和指向的内容都不可修改

const int* const p = &a;  // 两边都有const

// *p = 100;        // 错误!内容只读
// p = &b;          // 错误!指针本身只读

口诀号前面找const,指向内容不让动;号后面找const,指针本身不变更

2.4 const修饰函数参数

作用:保护传入的参数不被函数修改

void print(const char* str) {
    // str[0] = 'A';  // 错误!不能修改
    printf("%s", str);
}

int main() {
    char s[] = "hello";
    print(s);        // 可以传入普通变量
    print("world");  // 也可以传入常量
    return 0;
}

2.5 const修饰全局变量

const int MAX = 100;        // 全局常量
const char* MSG = "OK";     // 字符串常量

int main() {
    // MAX = 200;  // 错误!
    return 0;
}

三、volatile关键字

3.1 volatile的作用

volatile告诉编译器:这个变量的值可能会被意想不到地改变,每次使用都必须从内存中重新读取,禁止编译器优化到寄存器中

3.2 变量存储位置与优化问题

正常情况下

  • 变量可以存储在CPU寄存器内存

  • 编译器为了效率,会将频繁访问的变量加载到寄存器中

  • 后续操作直接读写寄存器,不再访问内存

这样做的问题

  • 如果变量在寄存器中的值没有写回内存,其他途径修改了内存中的值,就会产生不一致

  • 程序感知不到变量的变化,导致逻辑错误

3.3 不使用volatile的后果

// 示例:等待中断修改flag
int flag = 0;  // 普通变量

void wait_for_flag() {
    while(flag == 0) {
        // 等待flag变为1
    }
}

编译器可能做的优化

  1. 第一次读取flag到寄存器(假设值为0)

  2. 判断寄存器值是否为0,成立则进入循环

  3. 后续循环不再读取内存,直接判断寄存器

  4. 即使中断修改了内存中的flag,寄存器值仍是0

  5. 程序陷入死循环,永远看不到flag的变化

3.4 使用volatile后的变化

volatile int flag = 0;  // 告诉编译器不要优化

void wait_for_flag() {
    while(flag == 0) {
        // 每次循环都从内存读取flag的最新值
    }
}

编译器行为

  • 不会将flag加载到寄存器中缓存

  • 每次访问flag都直接从内存地址读取

  • 能及时感知到中断或其他线程对flag的修改

  • 虽然速度稍慢,但保证了正确性

3.5 volatile与寄存器/内存的关系

对比项普通变量volatile变量
存储位置可能缓存在寄存器始终在内存中操作
访问频率频繁访问用寄存器每次都用内存
读取时机首次读取后可能不再读每次都重新读取
性能较低(内存访问慢于寄存器)
正确性可能感知不到变化总能感知变化

3.6 什么时候用volatile

三种典型场景

1.被中断程序改变的变量

volatile int interrupt_flag = 0;

void interrupt_handler() {
    interrupt_flag = 1;  // 中断中修改
}

void main_loop() {
    while(!interrupt_flag) {
        // 等待中断
    }
}

2.多线程共享的变量

volatile int thread_running = 1;

void thread_func() {
    while(thread_running) {
        // 线程工作
    }
}

// 主线程可以修改thread_running来停止子线程

C语言标准本身并不直接支持多线程(这是由操作系统或第三方库提供的),但在多线程程序中,使用volatile可以确保一个线程对共享变量的修改能够立即对其他线程可见。然而,volatile并不保证操作的原子性,也不提供互斥访问,因此在多线程程序中通常需要与适当的同步机制(如互斥锁,信号量)结合使用。

volatile在多线程中的真实作用:

  1. 能做的:保证变量的可见性,防止编译器优化

  2. 不能做的:保证操作的原子性,防止指令重排

  3. 适用范围:简单的标志变量(一个线程只写,一个线程只读)

多线程编程的正确姿势:

  • 简单标志 → volatile(谨慎使用)

  • 计数器 → 原子操作

  • 复杂数据 → 互斥锁

  • 既要性能又要安全 → 读写锁、无锁数据结构

3.硬件寄存器

#define GPIO_STATUS ((volatile uint32_t*)0x40020000)
// 硬件可能随时改变这个寄存器的值
uint32_t status = *GPIO_STATUS;  // 每次都要重新读取

四、三关键字对比

4.1 核心区别

关键字核心作用影响对象使用场景
static隐藏/持久作用域、生命周期计数器、私有函数
const只读保护变量的可写性常量、参数保护
volatile防优化变量的访问方式中断、多线程、硬件

4.2 修饰对象对比

修饰对象staticconstvolatile
局部变量值持久化只读防优化(每次读内存)
全局变量文件私有只读防优化(每次读内存)
函数文件私有返回值只读不支持
指针不支持指针/内容只读指向的内容防优化
参数不支持参数只读参数防优化

五、一句话总结

  • static:让局部变量"长生不老",让全局变量和函数"隐姓埋名"

  • const:给变量贴上"只读"标签,并区分是"指向的内容不能动"还是"指针本身不能动"

  • volatile:告诉编译器"别把变量放到寄存器缓存,每次都去内存读取最新值"

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/weixin_46716828/article/details/159240994

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--