[C语言]解析const,、static、volatile

一、const, static与volatile

1、CONST

const的用法(定义和用途):Const就是常量修饰符, const 变量应该在声明的时候就进行初始化,如果在声明 常量的适合没有提供值,则该常量的值是不确定的,且无法修改。
const 修饰主要用来修饰变量、函数形参和类成员函数:
        1)const 常量:定义时就初始化,以后不能更改。
        2)const 形参: func(const int a);该形参在函数里不能改变
        3)const 修饰类成员函数:该函数对成员变量只能进行只读操作,就是 const类成员函数是不能修改成员变量的数值的。
基本作用:​const ​用于定义常量,如果一个变量被 const ​修饰,那么它的值就不能再被改变。
修饰:一般变量
const int n = 5;
// 或者
int const n = 5;
// n的值不能被改变  如n = 6;

修饰:常量指针与指针常量

int a = 5;
const int *n = &a;
// 或者
int const *n = &a;
// 或者
int *const n = &a;
// 或者
const int *const n = &a;

//const int *n = &a;​ 与 int const *n = &a;​,这两种写法是等价的,都表示 n ​所指向的地址可以被改变,但指向地址中的值不能被改变,被称为常量指针:
n = &b;//n 所指向的地址可以被改变;

//​int *const n = &a;​,表示 n ​所指向地址中值可以被改变,但指向的地址不能被改变,被称为指针常量
 *n = b;//n 所指向地址的值可以被改变

//​const int *const n = &a;​,是以上两种的结合版,表示 n ​的所指向的地址,以及所指向地址中的值,均不能被改变,被称为指向常量的常指针:
// 错误,n 所指向的地址不能被改变;
// n = &b;
// *n = b;

修饰:函数参数与修饰函数返回值
        当 const ​关键字修饰函数的参数和返回值时,作用同样是防止修改。

修饰:函数参数
        在传值调用时,const 修饰参数表示防止修改参数的值。
        在传址调用时,const 修饰参数的具体意义根据该参数具体是常量指针还是指针常量或是指向常量的常量指针有所不同。
        常量指针作为参数是防止在函数中修改其指向常量的值,例如:char *strcpy(char *dest, const char *src);
        假如定义该函数时,修改参数 src ​的指向地址中的值,编译器就会报错。指针常量作为参数,是防止在函数中修改其指向的地址,例如:void swap ( int * const p1 , int * const p2 )
假如在定义该函数时,修改了参数 p1 ​和 p2 ​所指向的地址,编译器就会报错。指向常量的常量指针作为参数,既是防止在函数中修改其指向的地址,也是防止在函数中修改其指向地址中的值。

修饰:函数返回值
        与 const 修饰参数时的作用相同,区别在于表示的是返回值的某些特性不可修改。

修饰:全局变量与修饰局部变量
        这里的区别在于,const 修饰局部变量和全局变量时,该变量所存储的区域有所区别

修饰:全局变量
        当 cost 修饰全局变量时,该变量会被存放在内存的常量区,而非一般全局变量所在的静态全局区。

修饰:局部变量
        当 const 修饰局部变量时,该变量会被存放在内存的栈区,当该函数执行完毕时,该变量所占用的内存会被回收释放。

2、STATIC

const的用法(定义和用途):

在 C 语言中, static作用:“改变生命周期”或者“改变作用域”。有以下特性 :

1、static局部变量:局部变量为动态存储,即指令执行到定义处才分配内存,将 一个变量声明为函数的局部变量, 使其变为静态存储方式(静态数据区), 那么这 个局部变量在函数执行完成之后不会被释放,而是继续保留在内存中。
2 static 全局变量: 全局变量即定义{}外面,其本身就是静态变量 ,编译时就分配内存,这只会改变其连接方式,使其只在本文件内部有效,而其他文件不可连接或引用该变量。
3、static 函数:对函数的连接方式产生影响,使得函数只在本文件内部有效, 对其他文件是不可见的。这样的函数又叫作静态函数。使用静态函数的好处是, 不用担心与其他文件的同名函数产生干扰,另外也是对函数本身的一种保护机 制。如果想要其他文件可以引用本地函数,则要在函数定义时使用关键字 extern, 表示该函数是外部函数,可供其他文件调用。另外在要引用别的文件中定义的外 部函数的文件中,使用 extern声明要用的外部函数即可。
基本作用:​static ​可以修饰局部变量,全局变量或者函数,改变其作用域或存储位置。
为变量进行初始化

        用 static 修饰的变量,会被自动初始化,对应的内存空间中,会被 0 值填充。


static int a;
static char c;
static void *p;
// a = 0
// c = 0
// p = 00000000
static 修饰在.c 文件中

// a.c
static int a = 100;
void print_a()
{
    printf("in a.c, static int a = %d\n", a);
}


//main.c
#include "a.h"
int main()
{
    // 错误,静态变量 a 只在 a.c 中生效
    // printf("a = %d\n", a);
    print_a();
    return 0;
}

// 输出
// in a.c, static int a = 100
static 修饰在.h 文件中
// a.h
static int a = 100;
extern void print_a();

// a.c
void print_a()
{
    printf("in a.c, static int &a = %p\n", &a);
}

//main.c
#include <stdio.h>
#include "a.h"
int main()
{
    printf("in main.c, static int &a = %p\n", &a);
    print_a();
    return 0;
}

// 输出:
// in main.c, static int &a = 0x56569008
// in a.c, static int &a = 0x5656900c

        建议不要在 .h ​文件中下定义任何变量,如果是全局的只使用 extern ​声明出来,在 .c ​下声明全局变量。当 static ​修饰局部变量时,该局部变量只会初始化一次。在函数运行结束后,该变量不会被销毁。 该变量的值会得到保留,下次继续调用该函数时,会用保留的值继续执行。

static ​所修饰的局部变量不在存储在栈区,而是存储在静态全局区。

3、VOLATILE

volatile的含义volatile的本意是“易变的”。在程序执行过程中,访问寄存器通常要比访问内存单元快得多。因此,编译器往往会进行优化,以减少对内存的存取次数,但这有可能导致读取到脏数据。

volatile的作用当使用volatile声明变量时,系统会确保每次都需要从该变量所在的内存地址重新读取数据,即使前面的指令刚刚从该地址读取过数据。换句话说,遇到这个关键字声明的变量时,编译器将不会对访问该变量的代码进行优化,从而保证了对该特殊地址的稳定访问。如果不使用volatile,编译器则可能会对相关的代码进行优化

3.1  在C语言中,volatile关键字是一种类型修饰符,用于告诉编译器不应对该变量的访问进行优化,以确保每次访问都是从内存中直接读取数据,而不是从缓存或寄存器中读取。

        下面是一个简单的例子,用于验证volatile关键字的作用。在这个例子中,我们将使用volatile关键字来防止编译器优化掉一个看似无用的循环,该循环实际上是在等待一个由外部事件(如中断)更改的变量。

#include <stdio.h>  
#include <unistd.h> // 包含usleep函数 
volatile int flag = 0; // 使用volatile修饰符  
  
void waitForFlag() {  
    while (!flag) {  
        // 空循环,等待flag被设置为1  
    }  
    printf("Flag is set!\n");  
}  
  
void setFlag() {  
    flag = 1; // 设置flag为1,这将导致waitForFlag函数中的循环退出  
}  
  
int main() {  
    printf("Waiting for flag to be set...\n");  
      
    // 在另一个线程或中断服务例程中,setFlag函数可能会被调用  
    // 但为了简化,我们在这里使用sleep来模拟延迟  
    usleep(2000000); // 等待2秒  
      
    setFlag(); // 设置flag,这将导致waitForFlag中的循环退出  
      
    waitForFlag(); // 实际上,由于setFlag的调用,这个函数会立即返回  
      
    return 0;  
}

        在这个例子中,flag变量被声明为volatile,这意味着编译器在编译时不会优化掉waitForFlag函数中的循环,即使看起来它像是在做无用功。如果去掉volatile修饰符,编译器可能会优化掉这个循环,因为它认为flag的值在循环内部没有改变,这会导致程序永远挂起,等待一个永远不会发生的条件。通过使用volatile,我们确保了每次循环迭代时都会从内存中重新读取flag的值,从而能够正确响应外部事件。

3.2  在嵌入式开发中,使用 volatile ​关键字是非常常见的,它常用于以下几种情况:

中断服务程序中的变量:当一个变量在中断服务程序(ISR)中被修改,并且这个变量的值需要被主程序或其他程序检测时,应该使用 volatile ​关键字。这是因为中断可能在任何时间发生,而编译器优化可能会使得主程序无法正确地读取到最新的变量值。
多任务环境下的共享标志:在多任务环境中,不同任务之间可能需要通过某些共享的标志来进行通信。这些标志应该被声明为 volatile​,以确保每次访问都是直接从内存中读取最新值,而不是从缓存或寄存器中获取可能已经过时的值。
存储器映射的硬件寄存器:对于映射到特定硬件寄存器的变量,它们可能在任意时刻由硬件异步修改。volatile​ 可以确保对这类变量的每次读写都会直接与硬件寄存器交互,防止编译器进行不恰当的优化。这样可以确保每次读写操作都不会被编译器优化掉,从而保证程序能够正确地与硬件设备通信。


// 1. 中断服务程序中修改的供其它程序检测的变量需要加volatile
volatile int flag = 0; // 定义一个volatile变量flag

void interrupt_service_routine() {
    flag = 1; // 在中断服务程序中修改flag的值
}

int main() {
    // 主程序检测flag的值
    if (flag) {
        printf("Interrupt occurred!\n");
    }
    return 0;
}


// 2. 多任务环境下各任务间共享的标志应该加volatile
volatile int shared_flag = 0; // 定义一个volatile变量shared_flag

void task1() {
    // 任务1中修改shared_flag的值
    shared_flag = 1;
}

void task2() {
    // 任务2中检测shared_flag的值
    if (shared_flag) {
        printf("Task 2 detected the change in shared_flag!\n");
    }
}


// 3. 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义
volatile uint32_t *hardware_register = (uint32_t *)0x40000000; // 存储器映射的硬件寄存器地址

void read_register() {
    uint32_t value = *hardware_register; // 读取硬件寄存器的值
    printf("Hardware register value: %u\n", value);
}

void write_register(uint32_t value) {
    *hardware_register = value; // 写入硬件寄存器的值
}

        此外,volatile ​关键字的作用是防止编译器对于被修饰的变量进行错误的优化。由于嵌入式系统通常涉及到实时性要求很高的操作,如中断处理和硬件控制,因此确保变量的每次访问都是最新的值至关重要。

        在嵌入式开发中,正确使用 volatile ​关键字对于确保系统的可靠性和稳定性是非常重要的。它帮助开发者避免因编译器优化而导致的潜在问题,确保程序能够准确地反映硬件状态和执行预期的操作。

二、总结

const

  • 定义和用途const是常量修饰符,用于声明一个变量的值在初始化之后不能被修改。
  • 主要用法
    • 修饰变量:定义时就初始化,之后不能更改。
    • 修饰函数形参:表示在函数内不能修改该参数的值。
    • 修饰类成员函数:表明该函数不会修改类的任何成员变量(常成员函数)。
    • 修饰指针:区分常量指针(指针指向的内容不可变)和指针常量(指针本身不可变)。
    • 修饰函数返回值:表明返回值的某些特性不可修改(如返回指向常量的指针)。
  • 存储位置
    • 修饰局部变量时,存储在栈区。
    • 修饰全局变量时,存储在常量区(与普通全局变量存储于静态全局区不同)。

static

  • 定义和用途static用于改变变量的生命周期或作用域,或者改变函数的连接属性。
  • 主要用法
    • 修饰局部变量:延长局部变量的生命周期,使其在函数调用结束后依然保留在内存中。
    • 修饰全局变量:限制全局变量的作用域,使其只在定义它的文件内部可见。
    • 修饰函数:限制函数的连接属性,使其只在定义它的文件内部可调用。
  • 初始化:被static修饰的变量会自动初始化为0(对于数值类型和指针)。
  • 存储位置:无论修饰局部变量还是全局变量,static变量都存储在静态存储区。

volatile

  • 定义和用途volatile告诉编译器该变量的值可能会以编译器难以预测的方式被改变,因此每次访问该变量时都必须直接从其内存地址中读取,而不能使用缓存中的值。
  • 主要用法
    • 用于中断服务程序中被修改的变量,确保主程序能读取到最新值。
    • 用于多任务环境中的共享标志,确保各任务间通信的可靠性。
    • 用于存储器映射的硬件寄存器,确保每次读写都直接与硬件交互。
  • 作用:防止编译器对变量的访问进行优化,确保程序的实时性和准确性,特别是在嵌入式系统和硬件交互密集的场景中。

        综上const, static与volatile,分别用于确保数据的不可变性(const)、控制变量的生命周期和可见性(static),以及确保变量访问的实时性和准确性(volatile)。

  • 25
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
回答: 在C语言中,auto、static、extern、register、constvolatile是用来定义变量和函数的存储类别和属性的关键字。\[2\] auto是默认的存储类别,用于定义自动变量,它的生命周期与所在的代码块相同。static用于定义静态变量,它的生命周期在程序运行期间都存在,但作用域仅限于所在的代码块。extern用于声明外部变量或函数,它表示该变量或函数是在其他文件中定义的。register用于定义寄存器变量,它的值存储在寄存器中,以提高访问速度。const用于定义常量,一旦被赋值后就不能再修改。volatile用于定义易变的变量,每次都会从内存中获取值,而不是从缓存中获取值,适用于对硬件寄存器的访问、多线程中访问全局变量和中断中访问非自动类型的变量。\[3\] 关键字constvolatile的使用可以提高程序的可读性和可靠性,因此在编写程序时,我们应该根据需要合理地使用这些关键字。 #### 引用[.reference_title] - *1* *2* [C中的auto、static、register、extern、const和volitate](https://blog.csdn.net/m0_70888041/article/details/128286925)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [c语言中的六个存储类型:auto register static extern const volatile](https://blog.csdn.net/a2998658795/article/details/125958396)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值