嵌入式的C/C++:深入理解 static、const 与 volatile 的用法与特点
目录
一、static
1、static 修饰局部变量
2、 static 修饰全局变量
3、static 修饰函数
4、static 修饰类成员
5、小结
二、const
1、const 修饰普通变量
2、const 修饰指针
3、const 修饰函数参数
4. const 修饰函数返回值
5. const 修饰类成员
6. const 与 #define 的比较
7. 小结
三、 volatile
1、volatile 的作用
2、volatile 的典型应用场景
3、volatile 的特性与限制
4、volatile 的用法
5、小结
在嵌入式的C/C++ 编程中,关键字不仅仅是语法结构的一部分,更是语言核心特性的体现。static、const 和 volatile 是三个常见且重要的关键字,广泛应用于变量管理、优化控制、代码安全性和硬件编程等领域。然而,很多开发者在使用它们时,往往只了解表面作用,而忽视了深入理解可能带来的性能优化和代码维护收益。本篇博客将通过细致的分类讲解和实用的示例,带你全面掌握这三个关键字的用法、特性和应用场景。

一、static
static 关键字有多种用途。在函数内部声明的变量前使用 static 关键字,可以让该变量在整个程序运行期间都保持其值,而不是在每次调用函数时重新初始化。对于全局变量或函数,static 可以限制它们的作用域到声明它们的文件内,即其他文件无法访问这些变量或函数。
1、static 修饰局部变量
作用:将局部变量的 生命周期 扩展为整个程序的运行期间,但 作用域 仍局限于函数内部
特点:初始化只会发生一次。再次调用函数时,保留变量上一次的值。
示例:
#include <stdio.h>
void counter() {static int count = 0; // 静态局部变量,初始化只执行一次count++;printf("Count: %d\n", count);
}int main() {counter(); // 输出:Count: 1counter(); // 输出:Count: 2counter(); // 输出:Count: 3return 0;
}
分析:
count是静态局部变量,第一次调用时初始化为 0。- 每次调用
counter函数后,count的值都会被保留,而不是销毁。 - 如果没有
static,count每次调用都会重新初始化为 0。
2、 static 修饰全局变量
作用:将全局变量的 作用域 限制在当前文件中,使得其他文件无法直接访问该变量。
特点:全局变量默认具有整个程序可见性,但加上 static 后,仅对声明它的文件可见。有助于模块化和避免命名冲突。
示例:
// file1.c
#include <stdio.h>
static int global_var = 100; // 静态全局变量,仅限于本文件void display() {printf("global_var: %d\n", global_var);
}// file2.c
#include <stdio.h>
extern int global_var; // 错误!无法访问 file1.c 中的静态全局变量int main() {display(); // 只能通过函数间接访问return 0;
}
分析:
global_var是静态全局变量,仅file1.c可访问,file2.c无法通过extern引用。- 模块化设计中,通过隐藏不必要的全局变量,减少模块间的耦合。
3、static 修饰函数
作用:将函数的作用域限制在当前文件中,避免外部文件调用该函数,起到“私有化”的作用。
特点:函数默认具有外部可见性,加上 static 后仅对当前文件可见。有助于避免命名冲突。
示例:
// file1.c
#include <stdio.h>
static void privateFunction() {printf("This is a static function.\n");
}void publicFunction() {privateFunction(); // 内部可以正常调用
}// file2.c
extern void privateFunction(); // 错误!无法访问 file1.c 中的静态函数int main() {publicFunction(); // 通过非静态函数间接调用return 0;
}
分析:
privateFunction是静态函数,仅file1.c内部可调用,file2.c无法通过extern声明使用。- 这种机制可以防止外部文件误调用函数,起到保护作用。
4、static 修饰类成员
4.1 静态成员变量
特点:属于整个类而非某个对象。在所有对象中共享,仅在程序中初始化一次。
示例:
#include <iostream>
class MyClass {
public:static int count; // 静态成员变量MyClass() { count++; }
};int MyClass::count = 0; // 静态成员变量初始化int main() {MyClass obj1, obj2, obj3;std::cout << "Object count: " << MyClass::count << std::endl; // 输出:3return 0;
}
4.2 静态成员函数
特点:不能访问非静态成员(因为没有对象实例)。可通过类名直接调用,而无需实例化对象。
示例:
#include <iostream>
class MyClass {
public:static void display() {std::cout << "This is a static member function." << std::endl;}
};int main() {MyClass::display(); // 静态成员函数直接通过类名调用return 0;
}
5、小结

static 关键字在模块化、性能优化和作用域管理中起到非常重要的作用。
二、const
const 关键字用于指定一个对象是常量,即它的值不能通过普通的赋值操作来改变。它可以应用于变量、指针、函数参数等。用于定义不可修改的值或具有只读属性的变量。它在程序设计中用于增强代码的安全性和可维护性。
1、const 修饰普通变量
作用:定义一个只读的变量,不能对其赋新值。
特点:变量的值在程序中固定。编译器会在尝试修改 const 变量时报错。
示例:
#include <stdio.h>
int main() {const int x = 10; // 定义只读变量printf("x = %d\n", x);// x = 20; // 错误:不能修改 const 变量return 0;
}
注意:const 修饰的变量必须初始化,否则会报错
2、const 修饰指针
在指针的定义中,const 的位置决定了指针的只读属性是指针本身还是指针指向的值。
2.1 指向的值是只读的
const int *p = &x; // 或 int const *p = &x;
含义:指针指向的值不能修改,但指针本身可以改变指向的地址。
示例:
#include <stdio.h>
int main() {int x = 10, y = 20;const int *p = &x; // 指针指向的值不可修改// *p = 15; // 错误:不能修改 p 指向的值p = &y; // 合法:可以修改 p 的指向printf("p points to: %d\n", *p);return 0;
}
2.2 指针本身是只读的
int * const p = &x;
含义:指针本身不能修改指向的地址,但指针指向的值可以改变。
示例:
#include <stdio.h>
int main() {int x = 10;int *const p = &x; // 指针本身不可修改*p = 20; // 合法:可以修改 p 指向的值// p = &y; // 错误:不能改变指针的指向printf("x = %d\n", *p);return 0;
}
2.3 指针本身和指向的值都是只读的
const int * const p = &x;
含义:指针本身和指向的值都不可修改。
3、const 修饰函数参数
作用:保护函数的输入参数,防止在函数内部被修改。
应用场景:适用于传入参数的值不应被修改的情况(例如输入只读配置、避免意外修改等)。
3.1 修饰值传递参数
void func(const int x) {// x = 20; // 错误:x 是只读的printf("x = %d\n", x);
}
int main() {func(10);return 0;
}
3.2 修饰指针参数
如果函数需要读取指针指向的值,但不能修改该值:
void display(const int *p) {// *p = 20; // 错误:不能修改 p 指向的值printf("Value: %d\n", *p);
}
如果函数不能修改指针本身的地址:
void display(int * const p) {// p = &y; // 错误:不能修改指针 p 本身的地址*p = 20; // 合法:可以修改 p 指向的值
}
4. const 修饰函数返回值
作用:防止函数返回值被修改。
4.1 修饰返回普通值
const int getValue() {return 10;
}
int main() {const int x = getValue();// x = 20; // 错误:不能修改 x 的值return 0;
}
4.2 修饰返回指针
如果函数返回一个指针,但指针指向的值不可修改:
const int* getPointer() {static int x = 10;return &x;
}
int main() {const int *p = getPointer();// *p = 20; // 错误:不能修改 p 指向的值return 0;
}
5. const 修饰类成员
5.1 修饰类成员变量
作用:类的成员变量定义为只读,必须在构造函数初始化列表中初始化。
#include <iostream>
class MyClass {
public:const int value; // const 成员变量MyClass(int v) : value(v) {} // 必须在构造函数初始化列表中初始化
};int main() {MyClass obj(10);// obj.value = 20; // 错误:不能修改 const 成员变量std::cout << "Value: " << obj.value << std::endl;return 0;
}
5.2 修饰类成员函数
作用:表示该函数不会修改类的成员变量。
语法:在函数定义后加 const。
#include <iostream>
class MyClass {
private:int data;
public:MyClass(int d) : data(d) {}int getData() const { // const 成员函数return data;}// void setData(int d) const { data = d; } // 错误:const 函数不能修改成员变量
};int main() {MyClass obj(10);std::cout << "Data: " << obj.getData() << std::endl;return 0;
}
6. const 与 #define 的比较
示例对比:
#define PI 3.14
const double pi = 3.14;int main() {// PI = 3.15; // 错误:#define 定义的值是文本替换,不是变量// pi = 3.15; // 错误:const 定义的值不能修改return 0;
}
7. 小结
const 的作用:
-
保护变量:限制变量或指针的可修改性,增强安全性。
-
优化函数:保护函数参数,避免意外修改。
-
增强表达力:通过
const声明,提高代码的可读性和维护性。 -
C++ 专用特性:在类中可修饰成员变量和成员函数,支持更复杂的只读逻辑。
const 是代码中保证不变性的强有力工具,在安全性、优化和可维护性方面至关重要。
三、 volatile
volatile 关键字用来修饰那些可能被意想不到地改变的变量,例如硬件寄存器中的值或并发线程中共享的变量。它告诉编译器不要对涉及这些变量的操作进行优化,确保每次读取都是从内存中读取最新的值。
1、volatile 的作用
防止编译器优化:
编译器在优化代码时,可能会将变量的值缓存在寄存器中,导致程序无法感知变量的实时变化。volatile 保证变量的值总是从内存中读取,而不是从寄存器缓存读取。
编译器在优化代码时,可能会做以下处理:
将变量的值缓存到寄存器中,避免重复访问内存。
在循环中,认为变量值不变,将其优化为常量。
volatile 禁止编译器对变量进行这些优化。
确保正确性:
对于可能被其他线程、中断服务程序(ISR)、硬件设备等修改的变量,volatile 确保程序访问的是最新值。
2、volatile 的典型应用场景
2.1 多线程编程
当一个变量可能被多个线程修改时,需要用 volatile 声明,以防止编译器优化。
volatile int flag = 0;void thread1() {while (flag == 0) {// 等待其他线程修改 flag}// 继续执行
}
void thread2() {flag = 1; // 修改 flag
}
如果没有 volatile,编译器可能会将 flag == 0 优化为一个死循环,因为它认为 flag 的值不会改变。
2.2 硬件寄存器访问
与硬件交互时,寄存器的值可能在程序之外发生变化,必须使用 volatile 确保程序访问到最新值。
#define STATUS_REGISTER *((volatile int *)0x40000000)void checkStatus() {while ((STATUS_REGISTER & 0x01) == 0) {// 等待硬件设置状态寄存器的第 0 位}// 状态已改变
}
2.3 中断服务程序 (ISR)
中断服务程序可能会修改主程序中的变量,因此这些变量需要声明为 volatile。
volatile int timer_flag = 0;void ISR() {timer_flag = 1; // 中断发生时修改变量
}int main() {while (timer_flag == 0) {// 等待中断}// 中断已发生return 0;
}
3、volatile 的特性与限制
作用:告诉编译器变量可能会被外部事件(硬件或线程)修改,因此每次访问变量时必须从内存中读取。
内存可见性:volatile 保证了 单线程环境 中的变量内存可见性,即从内存中读取最新值,但 不提供线程安全性(需要配合锁或原子操作)。
无法保证线程安全:volatile 仅保证变量值从内存中读取,但无法保证多个线程对同一变量的原子操作。例如:
volatile int counter = 0;void increment() {counter++; // 非线程安全,可能发生数据竞争
}
此时需要使用 互斥锁 。
无法控制操作顺序:
例如在多核处理器中,内存屏障(memory barrier)需要单独使用,volatile 无法确保操作的顺序。
4、volatile 的用法
4.1修饰普通变量
volatile int counter = 0;void modifyCounter() {counter++; // 确保每次操作都从内存读取
}
4.2 修饰指针
指针本身是 volatile:
int * volatile p;
含义:指针地址可能发生变化,但指针指向的值可以改变。
指针指向的值是 volatile:
volatile int *p;
含义:指针指向的值是可变的,必须从内存读取。
指针本身和指向的值都是 volatile:
volatile int * volatile p;
含义:指针本身和指针指向的值都可能被外部修改。
4.3缓存优化问题
假设没有使用 volatile:
int flag = 0;void waitForFlag() {while (flag == 0) {// 循环等待}
}
在某些编译器中,while (flag == 0) 可能被优化为:
if (flag == 0) {while (true) {} // 死循环
}
因为编译器假定 flag 不会被外部修改。
添加 volatile 后:
volatile int flag = 0;void waitForFlag() {while (flag == 0) {// 每次读取最新的 flag 值}
}
4.4硬件寄存器问题
#define STATUS_REG *((volatile int *)0x40000000)void pollStatus() {while ((STATUS_REG & 0x01) == 0) {// 等待硬件设置状态寄存器的第 0 位}
}
如果没有 volatile,编译器可能优化为:
int reg = STATUS_REG;
while ((reg & 0x01) == 0) {// 死循环,无法感知硬件变化
}
5、小结

注意事项:
-
对于需要确保线程安全的操作,需要配合 互斥锁 或 原子操作。
-
硬件编程中,寄存器值必须声明为
volatile,否则可能导致严重的逻辑错误。
volatile 是嵌入式系统和并发编程中不可或缺的关键字,在适当的场景下使用它能够显著提高代码的正确性和健壮性。
相关文章:
嵌入式的C/C++:深入理解 static、const 与 volatile 的用法与特点
目录 一、static 1、static 修饰局部变量 2、 static 修饰全局变量 3、static 修饰函数 4、static 修饰类成员 5、小结 二、const 1、const 修饰普通变量 2、const 修饰指针 3、const 修饰函数参数 4. const 修饰函数返回值 5. const 修饰类成员 6. const 与 #defi…...
信创改造 - TongRDS 替换 Redis
记得开放 6379 端口哦 1)首先在服务器上安装好 TongRDS 2)替换 redis 的 host,post,passwd 3)TongRDS 兼容 jedis # 例如:更改原先 redis 中对应的 host,post,passwd 改成 TongRDS…...
周志华深度森林deep forest(deep-forest)最新可安装教程,仅需在pycharm中完成,超简单安装教程
1、打开pycharm 没有pycharm的,在站内搜索安装教程即可。 2、点击“文件”“新建项目” 3、创建项目,Python版本中选择Python39。如果没有该版本,选择下面的Python 3.9下载并安装。 4、打开软件包,搜索“deep-forest”软件包&am…...
python VS c++
一、语法特点 Python: 语法简洁、优雅,代码可读性极强,采用缩进来表示代码块,摒弃了像 C 那样使用大括号的传统方式,使得代码看上去十分清晰简洁。例如: if 5 > 3:print("5大于3") elif 5 …...
提升软件测试报告的质量:Allure2中添加用例失败截图、日志、HTML块和视频的方法
Allure2的用途 Allure2是一个用于生成测试报告的框架,广泛应用于自动化测试和手动测试中。它支持多种测试框架,如JUnit、TestNG、MSTest等,通过生动的图表和详细的日志,使得非技术人员也能轻松地理解测试结果。许多团队选用Allur…...
基于IPMI的服务器硬件监控指标解读
在现代化数据中心中,服务器的稳定运行对于保障业务连续性至关重要。为了实时掌握服务器的健康状况,运维团队需要借助高效的监控工具。监控易作为一款功能强大的监控软件,支持使用IPMI(Intelligent Platform Management Interface&…...
VUE字符串转日期加天数
文章为本新手菜鸡的问题记录,如有错误和不足还行大佬指正 文章目录 问题描述解决方法 问题描述 得到一串字符串的日期,因为不是规范的日期格式,无法使用moment().add()方法,那么如何实现增加天数的操作? 解决方法 1…...
Android12 mtk设置插充电器自动开机
Android12 mtk平台通常关机后,插上充电器是进入关机充电流程,显示关机充电动画。 那么根据用户需求,如果需要设置关机之后,实现插上充电器后,自动开机。 正常流程:机器关机 --> 插上充电器 --> 显示…...
JSON路径工具类`JsonPathUtil`的实现与应用
JSON路径工具类JsonPathUtil的实现与应用 作者:zibo 日期:2024/11/25 口号:慢慢学,不要停。 文章目录 JSON路径工具类JsonPathUtil的实现与应用〇、完整代码一、引言二、功能概述三、代码实现详解1. 工具类基础结构2. 核心方法get…...
人名分类器(nlp)
# coding: utf-8 import osos.environ[KMP_DUPLICATE_LIB_OK] True# 导入torch工具 import jsonimport torch # 导入nn准备构建模型 import torch.nn as nn import torch.nn.functional as F import torch.optim as optim # 导入torch的数据源 数据迭代器工具包 from torch.ut…...
斐波那契数列 相关问题 详解
斐波那契数列相关问题详解 斐波那契数列及其相关问题是算法学习中的经典主题,变形与应用非常广泛,涵盖了递推关系、动态规划、组合数学、数论等多个领域。以下是斐波那契数列的相关问题及其解法的详解。 1. 经典斐波那契数列 定义 初始条件࿱…...
Pytorch微调深度学习模型
在公开数据训练了模型,有时候需要拿到自己的数据上微调。今天正好做了一下微调,在此记录一下微调的方法。用Pytorch还是比较容易实现的。 网上找了很多方法,以及Chatgpt也给了很多方法,但是不够简洁和容易理解。 大体步骤是&…...
springboot 使用笔记
1.springboot 快速启动项目 注意:该启动只是临时启动,不能关闭终端面板 cd /www/wwwroot java -jar admin.jar2.脚本启动 linux shell脚本启动springboot服务 3.java一键部署springboot 第5条 https://blog.csdn.net/qq_30272167/article/details/1…...
网络安全基础——网络安全法
填空题 1.根据**《中华人民共和国网络安全法》**第二十条(第二款),任何组织和个人试用网路应当遵守宪法法律,遵守公共秩序,遵守社会公德,不危害网络安全,不得利用网络从事危害国家安全、荣誉和利益,煽动颠…...
SCAU软件体系结构实验四 组合模式
目录 一、题目 二、源码 一、题目 个人(Person)与团队(Team)可以形成一个组织(Organization):组织有两种:个人组织和团队组织,多个个人可以组合成一个团队,不同的个人与团队可以组合成一个更大的团队。 使用控制台或者JavaFx界面…...
Amazon商品详情API接口:电商创新与用户体验的驱动力
在电子商务蓬勃发展的今天,作为全球最大的电商平台之一,亚马逊(Amazon)凭借其强大的技术实力和丰富的商品资源,为全球用户提供了优质的购物体验。其中,Amazon商品详情API接口在电商创新与用户体验提升方面扮…...
手机无法连接服务器1302什么意思?
你有没有遇到过手机无法连接服务器,屏幕上显示“1302”这样的错误代码?尤其是在急需使用手机进行工作或联系朋友时,突然出现的连接问题无疑会带来不少麻烦。那么,什么是1302错误,它又意味着什么呢? 1302错…...
Android adb shell dumpsys audio 信息查看分析详解
Android adb shell dumpsys audio 信息查看分析详解 一、前言 Android 如果要分析当前设备的声音通道相关日志, 仅仅看AudioService的日志是看不到啥日志的,但是看整个audio关键字的日志又太多太乱了, 所以可以看一下系统提供的一个调试指令…...
Python 网络爬虫操作指南
网络爬虫是自动化获取互联网上信息的一种工具。它广泛应用于数据采集、分析以及实现信息聚合等众多领域。本文将为你提供一个完整的Python网络爬虫操作指南,帮助你从零开始学习并实现简单的网络爬虫。我们将涵盖基本的爬虫概念、Python环境配置、常用库介绍。 上传…...
基于FPGA的2FSK调制-串口收发-带tb仿真文件-实际上板验证成功
基于FPGA的2FSK调制 前言一、2FSK储备知识二、代码分析1.模块分析2.波形分析 总结 前言 设计实现连续相位 2FSK 调制器,2FSK 的两个频率为:fI15KHz,f23KHz,波特率为 1500 bps,比特0映射为f 载波,比特1映射为 载波。 1)…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving
地址:LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂,正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...
Spring Security 认证流程——补充
一、认证流程概述 Spring Security 的认证流程基于 过滤器链(Filter Chain),核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤: 用户提交登录请求拦…...
大模型——基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程
基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程 下载安装Docker Docker官网:https://www.docker.com/ 自定义Docker安装路径 Docker默认安装在C盘,大小大概2.9G,做这行最忌讳的就是安装软件全装C盘,所以我调整了下安装路径。 新建安装目录:E:\MyS…...
Python的__call__ 方法
在 Python 中,__call__ 是一个特殊的魔术方法(magic method),它允许一个类的实例像函数一样被调用。当你在一个对象后面加上 () 并执行时(例如 obj()),Python 会自动调用该对象的 __call__ 方法…...
Docker、Wsl 打包迁移环境
电脑需要开启wsl2 可以使用wsl -v 查看当前的版本 wsl -v WSL 版本: 2.2.4.0 内核版本: 5.15.153.1-2 WSLg 版本: 1.0.61 MSRDC 版本: 1.2.5326 Direct3D 版本: 1.611.1-81528511 DXCore 版本: 10.0.2609…...
十二、【ESP32全栈开发指南: IDF开发环境下cJSON使用】
一、JSON简介 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,具有以下核心特性: 完全独立于编程语言的文本格式易于人阅读和编写易于机器解析和生成基于ECMAScript标准子集 1.1 JSON语法规则 {"name"…...
SeaweedFS S3 Spring Boot Starter
SeaweedFS S3 Spring Boot Starter 源码特性环境要求快速开始1. 添加依赖2. 配置文件3. 使用方式方式一:注入服务类方式二:使用工具类 API 文档SeaweedFsS3Service 主要方法SeaweedFsS3Util 工具类方法 配置参数运行测试构建项目注意事项集成应用更多项目…...
【Unity】R3 CSharp 响应式编程 - 使用篇(二)
一、通用的事件监听用法 using System;using R3;using UnityEngine;namespace Aladdin.Standard.Observable.Common{public class CommonObservable : MonoBehaviour{// 默认会调用1次public SerializableReactiveProperty<int> serializableReactiveProperty;…...

