嵌入式的C/C++:深入理解 static、const 与 volatile 的用法与特点

news2024/11/27 0:08:08

目录

一、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++ 编程中,关键字不仅仅是语法结构的一部分,更是语言核心特性的体现。staticconstvolatile 是三个常见且重要的关键字,广泛应用于变量管理、优化控制、代码安全性和硬件编程等领域。然而,很多开发者在使用它们时,往往只了解表面作用,而忽视了深入理解可能带来的性能优化和代码维护收益。本篇博客将通过细致的分类讲解和实用的示例,带你全面掌握这三个关键字的用法、特性和应用场景。

一、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: 1
    counter();  // 输出:Count: 2
    counter();  // 输出:Count: 3
    return 0;
}

分析

  1. count 是静态局部变量,第一次调用时初始化为 0。
  2. 每次调用 counter 函数后,count 的值都会被保留,而不是销毁。
  3. 如果没有 staticcount 每次调用都会重新初始化为 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;
}

分析

  1. global_var 是静态全局变量,仅 file1.c 可访问,file2.c 无法通过 extern 引用。
  2. 模块化设计中,通过隐藏不必要的全局变量,减少模块间的耦合。

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;
}

分析

  1. privateFunction 是静态函数,仅 file1.c 内部可调用,file2.c 无法通过 extern 声明使用。
  2. 这种机制可以防止外部文件误调用函数,起到保护作用。

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; // 输出:3
    return 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 的作用:

  1. 保护变量:限制变量或指针的可修改性,增强安全性。

  2. 优化函数:保护函数参数,避免意外修改。

  3. 增强表达力:通过 const 声明,提高代码的可读性和维护性。

  4. 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、小结

注意事项:

  1. 对于需要确保线程安全的操作,需要配合 互斥锁原子操作

  2. 硬件编程中,寄存器值必须声明为 volatile,否则可能导致严重的逻辑错误。

volatile 是嵌入式系统和并发编程中不可或缺的关键字,在适当的场景下使用它能够显著提高代码的正确性和健壮性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2248103.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

云计算-华为HCIA-学习笔记

笔者今年7月底考取了华为云计算方向的HCIE认证&#xff0c;回顾从IA到IE的学习和项目实战&#xff0c;想整合和分享自己的学习历程&#xff0c;欢迎志同道合的朋友们一起讨论&#xff01; 第三章&#xff1a;常见设备 交换机 二层交换机和三层交换机&#xff0c;所谓二层交换机…

基于FPGA的2FSK调制-串口收发-带tb仿真文件-实际上板验证成功

基于FPGA的2FSK调制 前言一、2FSK储备知识二、代码分析1.模块分析2.波形分析 总结 前言 设计实现连续相位 2FSK 调制器&#xff0c;2FSK 的两个频率为:fI15KHz&#xff0c;f23KHz&#xff0c;波特率为 1500 bps,比特0映射为f 载波&#xff0c;比特1映射为 载波。 1&#xff09…

网络安全与加密

1.Base64简单说明描述&#xff1a;Base64可以成为密码学的基石&#xff0c;非常重要。特点&#xff1a;可以将任意的二进制数据进行Base64编码结果&#xff1a;所有的数据都能被编码为并只用65个字符就能表示的文本文件。65字符&#xff1a;A~Z a~z 0~9 / 对文件进行base64编码…

Python绘制太极八卦

文章目录 系列目录写在前面技术需求1. 图形绘制库的支持2. 图形绘制功能3. 参数化设计4. 绘制控制5. 数据处理6. 用户界面 完整代码代码分析1. rset() 函数2. offset() 函数3. taiji() 函数4. bagua() 函数5. 绘制过程6. 技术亮点 写在后面 系列目录 序号直达链接爱心系列1Pyth…

C 语言面向对象

面向对象的基本特性&#xff1a;封装&#xff0c;继承&#xff0c;多态 1.0 面向过程概念 当我们在编写程序时&#xff0c;通常采用以下步骤&#xff1a; 1. 将问题的解法分解成若干步骤 2. 使用函数分别实现这些步骤 3. 依次调用这些函数 这种编程风格的被称作 面向过程…

滑动窗口最大值(java)

题目描述 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1&#xff1a; 输入&#xff1a;nums [1,3,-1,-3,5,3,6,7]…

拥抱极简主义前端开发:NoCss.js 引领无 CSS 编程潮流

在前端开发的世界里&#xff0c;我们总是在不断追寻更高效、更简洁的方式来构建令人惊艳的用户界面。而今天&#xff0c;我要向大家隆重介绍一款具有创新性的工具 ——NoCss.js&#xff0c;它将彻底颠覆你对传统前端开发的认知&#xff0c;引领我们进入一个全新的无 CSS 编程时…

配置Springboot+vue项目在ubuntu20.04

一、jdk1.8环境配置 (1) 安装jdk8&#xff1a; sudo apt-get install openjdk-8-jdk (2) 检查jdk是否安装成功&#xff1a; java -version(3) 设置JAVA_HOME&#xff1a; echo export JAVA_HOME/usr/lib/jvm/java-17-openjdk-amd64 >> ~/.bashrc echo export PATH$J…

Spring框架特性及包下载(Java EE 学习笔记04)

1 Spring 5的新特性 Spring 5是Spring当前最新的版本&#xff0c;与历史版本对比&#xff0c;Spring 5对Spring核心框架进行了修订和更新&#xff0c;增加了很多新特性&#xff0c;如支持响应式编程等。 更新JDK基线 因为Spring 5代码库运行于JDK 8之上&#xff0c;所以Spri…

软考教材重点内容 信息安全工程师 第 5 章 物理与环境安全技术

5.1.1 物理安全概念 传统上的物理安全也称为实体安全&#xff0c;是指包括环境、设备和记录介质在内的所有支持网络信息系统运行的硬件的总体安全&#xff0c;是网络信息系统安全、可靠、不间断运行的基本保证&#xff0c;并且确保在信息进行加工处理、服务、决策支持的过程中&…

「Chromeg谷歌浏览器/Edge浏览器」篡改猴Tempermongkey插件的安装与使用

1. 谷歌浏览器安装及使用流程 1.1 准备篡改猴扩展程序包。 因为谷歌浏览器的扩展商城打不开&#xff0c;所以需要准备一个篡改猴压缩包。 其他浏览器只需打开扩展商城搜索篡改猴即可。 没有压缩包的可以进我主页下载。 也可直接点击下载&#xff1a;Chrome浏览器篡改猴(油猴…

【案例学习】如何使用Minitab实现包装过程的自动化和改进

Masimo 是一家全球性的医疗技术公司&#xff0c;致力于开发和生产各种行业领先的监控技术&#xff0c;包括创新的测量、传感器和患者监护仪。在 Masimo Hospital Automation 平台的助力下&#xff0c;Masimo 的连接、自动化、远程医疗和远程监控解决方案正在改善医院内外的护理…

Git旧文件覆盖引发思考

一天&#xff0c;我的同事过来找到我&#xff0c;和我讲&#xff1a;张叫兽&#xff0c;大事不好&#xff0c;我的文件被人覆盖了。git是真的不好用啊 git不好用&#xff1f;文件被覆盖&#xff1b;瞬间我似乎知道了什么&#xff0c;让我想到了某位男明星的语法&#xff1a;他…

CSP/信奥赛C++语法基础刷题训练(23):洛谷P1217:[USACO1.5] 回文质数 Prime Palindromes

CSP/信奥赛C语法基础刷题训练&#xff08;23&#xff09;&#xff1a;洛谷P1217&#xff1a;[USACO1.5] 回文质数 Prime Palindromes 题目描述 因为 151 151 151 既是一个质数又是一个回文数&#xff08;从左到右和从右到左是看一样的&#xff09;&#xff0c;所以 151 151 …

嵌入式系统与OpenCV

目录 一、OpenCV 简介 二、嵌入式 OpenCV 的安装方法 1. Ubuntu 系统下的安装 2. 嵌入式 ARM 系统中的安装 3. Windows10 和树莓派系统下的安装 三、嵌入式 OpenCV 的性能优化 1. 介绍嵌入式平台上对 OpenCV 进行优化的必要性。 2. 利用嵌入式开发工具&#xff0c;如优…

SAP BC 记录一次因为HANA服务器内存满的问题

用户操作 DB02 进入hana数据库服务器 free -g 内存用完了 如下图 解决方案&#xff1a;增加内存 操作 关应用服务->关闭数据库服务->关闭hana服务器->加内存->起hana服务器->起hana服务->启动应用服务。

ArcGIS应用指南:ArcGIS制作局部放大地图

在地理信息系统&#xff08;GIS&#xff09;中&#xff0c;制作详细且美观的地图是一项重要的技能。地图制作不仅仅是简单地将地理数据可视化&#xff0c;还需要考虑地图的可读性和美观性。局部放大图是一种常见的地图设计技巧&#xff0c;用于展示特定区域的详细信息&#xff…

python画图|无坐标轴自由划线操作fig.add_artist(lines.Line2D()函数

【1】引言 新发现了一种自由划线操作函数&#xff0c;和大家共享。 【2】官网教程 点击下述代码&#xff0c;直达官网&#xff1a; https://matplotlib.org/stable/gallery/misc/fig_x.html#sphx-glr-gallery-misc-fig-x-py 官网代码非常简洁&#xff0c;我进行了解读。 …

深度解析:Nginx模块架构与工作机制的奥秘

文章目录 前言Nginx是什么?Ngnix特点&#xff1a; 一、Nginx模块与工作原理1.Nginx的模块1.1 Nginx模块常规的HTTP请求和响应的流程图:1.2 Nginx的模块从结构上分为如下三类&#xff1a;1.3 Nginx的模块从功能上分为如下三类: 2.Nginx的进程模型2.1 Nginx进程结构2.2 nginx进程…

抖音SEO矩阵系统:开发技术分享

市场环境剖析 短视频SEO矩阵系统是一种策略&#xff0c;旨在通过不同平台上的多个账号建立联系&#xff0c;整合同一品牌下的各平台粉丝流量。该系统通过遵循每个平台的规则和内容要求&#xff0c;输出企业和品牌形象&#xff0c;以矩阵形式增强粉丝基础并提升商业价值。抖音作…