嵌入式的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)…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...
SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...

