volatile 使用误区

本文探讨了在嵌入式开发中使用typedef定义指向volatile变量的指针时常见的错误做法,并介绍了正确的定义方式。此外,还展示了如何利用特殊寄存器实现外设的访问。

在建立编译环境的时候用typedef定义了指向volatile 单元的指针,最后终于发现行不通。

typedef char              u8;
typedef unsigned short    u16;
typedef unsigned int      u32;

typedef volatile u8       vu8;
typedef volatile u16      vu16;
typedef volatile u32      vu32;

typedef char  *           p8;
typedef unsigned short *  p16;
typedef unsigned int *    p32;

typedef char volatile *            vp8;
typedef unsigned short volatile *  vp16;
typedef unsigned int volatile *    vp32;

原来上述三行定义为

typedef volatile p8            vp8;
typedef volatile p16          vp16;
typedef volatile p32          vp32;

蓝色的部分语法通过了,但是volatile根本不起作用。

 

 

另外提一下寄存器还可以这样定义:

typedef uint32_t special_register;

typedef struct UART UART
struct UART
{
  special_register ULCON; 
  special_register UCON;    /*控制*/
  special_register USTAT;     /*状态*/
  special_register UTXBUF;    /*发送缓冲*/
  special_register URXBUF;    /*接收缓冲*/
  special_register UBRDIV;    
};


http://xjbclz.banzhu.net/article/xjbclz-16-880646.html

 

UART0对应的特殊寄存器被映射到0x03FFD000。我们有两种方法来访问该寄存器,一种是宏定义方法:
#define UART0 (( UART volatile *) 0x03FFD000)

另一种是通过常量指针:
UART volatile *const UART0  = ( UART volatile *)0x03FFD000;

 

<think>好的,用户让我介绍一下用volatile修饰数组的情况。我得先回想一下volatile关键字在C/C++中的作用。记得volatile主要是告诉编译器这个变量可能会被意外修改,所以编译器不能做优化,比如缓存到寄存器或者重排指令。那当它修饰数组时,具体会发生什么呢? 首先,得区分数组本身和数组元素。如果声明数组时用了volatile,比如volatile int arr[10],那每个元素都是volatile的。这样的话,每次访问数组元素时,编译器都会从内存中重新读取,而不是用之前缓存的值。这对于硬件寄存器或者多线程共享的数据特别重要,确保每次访问都是最新的值。 接下来要考虑数组作为指针的情况。如果有一个指向volatile int的指针,比如volatile int* ptr,那么ptr指向的数组元素也是volatile的。这里要注意,指针本身的volatile性可能和数组的不同,需要分开处理。比如指针本身可能是volatile,而指向的数据不是,或者反过来,这得看具体的声明方式。 然后,多维数组的情况。比如volatile int arr2D[5][5],这时候每个元素都是volatile的。访问任何一个元素都需要保证直接从内存读写,防止编译器优化导致的问题。 还有一个需要注意的点是函数参数传递。如果函数参数是volatile数组,实际上会被转换为指针。比如void func(volatile int arr[]),其实等价于void func(volatile int* arr)。这时候在函数内部,通过指针访问数组元素时,同样要遵守volatile的规则,确保每次访问都是实际的读取或写入。 应用场景方面,主要是在嵌入式或硬件编程中。比如访问内存映射的硬件寄存器,这些寄存器的值可能随时改变,使用volatile数组可以确保程序正确读取硬件状态。另外,在多线程环境中,如果多个线程共享一个数组,虽然volatile不能替代互斥锁,但可以保证可见性,即一个线程的修改对其他线程立即可见。不过这里可能需要提醒用户,volatile不保证原子性,线程安全还需要其他机制。 容易出错的地方可能包括:误以为volatile可以解决所有并发问题,或者忘记volatile修饰导致编译器优化引发的问题。比如,循环中检查数组元素作为退出条件,如果没有volatile,编译器可能会优化成只读一次,导致无限循环。 总结下来,需要明确volatile修饰数组时,每个元素都具备volatile属性,确保每次访问都是直接操作内存。同时,要注意在函数参数中的转换,以及正确应用在硬件访问和多线程环境中的场景,同时指出其局限性。</think>关于使用`volatile`修饰数组的问题,需要从语法、语义和应用场景逐步分析: 一、基本概念 1. **`volatile`关键字的作用** 告知编译器该变量可能被意外修改(如硬件寄存器、多线程共享变量),禁止编译器对其进行优化: - 每次访问必须从内存读取最新值 - 禁止指令重排(需配合内存屏障) 2. **修饰数组时的特殊性** 数组名本身是地址常量,但`volatile`实际作用于数组元素而非地址本身 二、具体语法形式 ```c volatile int arr[10]; // 每个元素都具有volatile属性 int volatile *ptr; // 指针指向volatile数据 volatile int *volatile ptr; // 指针本身和指向数据均为volatile ``` 三、关键行为特征 1. **元素级volatile** 每个数组元素的读写都会触发实际内存访问,例如: ```c volatile int sensor_data[5]; for(int i=0; i<5; i++) { printf("%d", sensor_data[i]); // 每次循环都会重新读取内存 } ``` 2. **函数参数传递** 当数组作为函数参数时,volatile属性需要显式声明: ```c void read_array(volatile int arr[], int size) { // 此处arr退化为volatile int*类型 } ``` 四、典型应用场景 1. **硬件寄存器访问** ```c // 内存映射的硬件寄存器数组 volatile uint32_t* const HW_REGS = (volatile uint32_t*)0x40000000; ``` 2. **多线程共享数据** (需配合其他同步机制) ```c volatile int shared_buffer[1024]; // 保证可见性,不保证原子性 ``` 五、常见误区 1. **与指针修饰的差异** 以下两种写法不等效: ```c volatile int* arr1[10]; // 指针数组,每个指针是volatile int* volatile arr2[10]; // 数组的每个指针本身是volatile ``` 2. **多维数组的特殊性** ```c volatile int matrix[5][5]; // 所有元素均具有volatile属性 ``` 六、编译器优化对比 未使用volatile: ```c int arr[10]; int sum = 0; for(int i=0; i<10; i++) sum += arr[i]; // 编译器可能优化为一次性读取整个数组 ``` 使用volatile: ```c volatile int arr[10]; // 编译器必须生成10次独立的内存访问指令 ``` 建议在实际使用时结合具体的硬件手册或线程同步需求,配合适当的Memory Barrier使用以保证执行顺序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值