[C语言]const, static与volatile

const, static与volatile

const

基本作用

const ​用于定义常量,如果一个变量被 const ​修饰,那么它的值就不能再被改变。

修饰变量

一般变量

写法:

const int n = 5;
// 或者
int const n = 5;

以上两种写法都表示变量 n ​的不能被改变

// 错误,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 ​所指向的地址可以被改变,但指向地址中的不能被改变,被称为常量指针

    int a = 5;
    int b = 10;
    const int *n = &a;
    // 或者
    // int const *n = &a;
    
    // 正确,n 所指向的地址可以被改变;
    n = &b;
    
    // 错误,n 所指向地址的值不能被改变
    // *n = b;
    
    printf("n = %d\n", *n);
    
    // 输出 n = 10
    
  • int *const n = &a;​,表示 n ​所指向地址中可以被改变,但指向的地址不能被改变,被称为指针常量

    int a = 5;
    int b = 10;
    int *const n = &a;
    
    // 错误,n 所指向的地址不能被改变;
    // n = &b;
    
    // 正确,n 所指向地址的值可以被改变
     *n = b;
    
     printf("n = %d\n", *n);
    
    // 输出 n = 10
    
  • const int *const n = &a;​,是以上两种的结合版,表示 n ​的所指向的地址,以及所指向地址中的均不能被改变,被称为指向常量的常指针

    int a = 5;
    const int *const n = &a;
    
    // 错误,n 所指向的地址不能被改变;
    // n = &b;
    
    // 错误,n 所指向地址的值不能被改变
    // *n = b;
    
    printf("n = %d\n", *n);
    
    // 输出 n = 10
    

修饰函数参数与修饰函数返回值

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

修饰函数参数
  • 在传值调用时,const 修饰参数表示防止修改参数的值。
  • 在传址调用时,const 修饰参数的具体意义根据该参数具体是常量指针还是指针常量或是指向常量的常量指针有所不同。
  1. 常量指针作为参数是防止在函数中修改其指向常量的值,例如

    char *strcpy(char *dest, const char *src)

    假如定义该函数时,修改参数 src ​的指向地址中的,编译器就会报错。

  2. 指针常量作为参数,是防止在函数中修改其指向的地址,例如:

    void swap ( int * const p1 , int * const p2 )
    

    假如在定义该函数时,修改了参数 p1 ​和 p2 ​所指向的地址,编译器就会报错。

  3. 指向常量的常量指针作为参数,既是防止在函数中修改其指向的地址,也是防止在函数中修改其指向地址中的

修饰函数返回值

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

修饰全局变量与修饰局部变量

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

修饰全局变量

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

修饰局部变量

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

static

基本作用

static ​可以修饰局部变量,全局变量或者函数,改变其作用域或存储位置。

为变量进行初始化

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

#include <stdio.h>

static int a;
static char c;
static void *p;

int main() {
    printf("a = %d\n",a);
    printf("c = %d\n",c);
    printf("p = %p\n",p);
    return 0;
}

// 输出
// a = 0
// c = 0
// p = 00000000

通过改变变量或函数的作用域来隐藏变量或函数

static 修饰在.c 文件中

当一个在 .c ​文件中的变量或函数被 static ​修饰时,该变量或函数的作用域就被限制在了该 .c ​文件中,其他文件无法访问该变量,该变量也无法被 extern ​修饰。

// a.h
#ifndef _A_H
#define _A_H

extern void print_a();

#endif //_A_H



// a.c
#include <stdio.h>
#include "a.h"

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 文件中

当一个在 .h ​文件中的变量或函数被 static ​修饰时,该变量会随着预处理过程中头文件展开时,在引用的不同的 .c ​文件中,生成不同的变量。

// a.h
#ifndef _A_H
#define _A_H
static int a = 100;

extern void print_a();

#endif //_A_H


// a.c
#include <stdio.h>
#include "a.h"

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 ​修饰局部变量时,该局部变量只会初始化一次。在函数运行结束后,该变量不会被销毁。 该变量的值会得到保留,下次继续调用该函数时,会用保留的值继续执行。

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

#include <stdio.h>

void print_x()
{
    static int x = 0;
    printf("static int x = %d\t", x);
    printf("static int &x = %p\n", &x);
    ++x;
}

int main()
{
    print_x();
    print_x();
    print_x();
    return 0;
}

// 输出
// static int x = 0	static int &x = 0x565a3010
// static int x = 1	static int &x = 0x565a3010
// static int x = 2	static int &x = 0x565a3010
static 修饰的全局变量与一般全局变量的区别

static ​修饰的全局变量和一般的全局变量都存储在程序的静态全局区,但是其作用域有所不同。

static ​修饰的全局变量只在当前 .c ​文件中有效,每一个 .c ​文件中变量的存储地址不同,因此可以在不同的 .c ​文件中以 static ​形式来声明同名的全局变量。

在不同的 .c ​文件中声明同名的一般全局变量,使用的是相同的存储空间,只能有一个 .c ​文件中对此变量进行初始化,此时连接不会出错。

volatile

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

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

  • 55
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言中的volatile关键字是用来声明一个变量为“易变”的。这意味着该变量的值可以在程序执行期间随时发生变化,可能由于硬件或者其他并行执行的线程的影响。使用volatile关键字可以告诉编译器不要对该变量进行优化,以确保每次访问该变量时都从内存中读取最新的值。这对于需要频繁读写硬件寄存器或者在多线程环境下共享的变量非常有用。 参考资料: C语言深度解析专栏—C语言关键字详解第五篇 C语言再学习 -- 关键字volatile_聚优致成的博客-CSDN博客 更多关键字在下面博客链接 C语言关键字详解(一)auto、register关键字 C语言关键字详解(二)带你全面了解 static C语言关键字详解(三)数据类型与sizeof关键字 C语言关键字详解(四)带你全面了解 const 关键字<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [C语言关键字详解(五)带你全面了解 volatile 关键字](https://blog.csdn.net/m0_62391199/article/details/123746218)[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^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [C语言volatile 关键字](https://blog.csdn.net/qq_58264156/article/details/127416196)[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^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值