对于(*(volatile unsigned char *)(x))的理解(转载新浪微博的一位博主)

本文详细解析了C语言中如何使用volatile关键字配合内存映射技术直接访问特定内存地址,尤其针对嵌入式系统中寄存器的读写操作进行了深入探讨。


在看vivi代码时,Nand_read.c文件中有下面一段

#define __REGb(x) (*(volatileunsigned char *)(x))
#define __REGi(x) (*(volatile unsigned int *)(x))

#define NF_BASE  0x4e000000
#define NFCONF  __REGi(NF_BASE + 0x0)
#define NFCMD  __REGb(NF_BASE + 0x4)
#define NFADDR  __REGb(NF_BASE + 0x8)
#define NFDATA  __REGb(NF_BASE + 0xc)
#define NFSTAT  __REGb(NF_BASE + 0x10)

把宏定义放入句中就是(*(volatileunsigned int *))(0x4e000000),不知其何意??好像是定义一指针,该指针指向的内容就是0x4e000000该寄存器的内容。

 

网上查了资料,先看英文的,看看外国人怎么解释:

Using C, I was trying toassign a variable name to a register address so that my code would be readable.An example of how to do this is as follows:

#define DDRA (*(volatile unsigned char*)(0x22))

This means that if aregister or memory location exists at address 0×22, I can use DDRA toread or write to it like so..

DDRA = 0x05

 

In my C code.The #define looks really cryptic atfirst. The way to understand this is by breaking it down into pieces.

First of all,

unsigned char

means we are using abyte-sized memory location. Byte being 8-bits wide.

unsigned char *

means we are declaring apointer that points to a byte-sized location.

(unsigned char *) (0x22)

means the byte-sized pointerpoints to address 0×22. TheC compiler will refer to address 0×22when the variable DDRA is used. The assembly code will end up using 0×22 in Load(LD) and Store (STR)insturctions.

(*(unsigned char *)(0x22))

The first asterisk from theleft signifies that we want to manipulate the value in address 0×22. * means “the value pointed to by the pointer”.

volatile

volatile forces the compilerto issue a Load or Store anytime DDRA is accessed as the value may changewithout the compiler knowing it.

 

再看看中文的解释:
   
使用一个32位处理器,要对一个32位的内存地址进行访问,可以这样定义

    #define RAM_ADDR     (*(volatileunsigned long *)0x0000555F)
    然后就可以用C语言对这个内存地址进行读写操作了
   
读:tmp= RAM_ADDR
   
写:RAM_ADDR= 0x55
定义volatile
是因为它的值可能会改变,大家都知道为什么改变了;
如果在一个循环操作中需要不停地判断一个内存数据,例如要等待RAM_ADDR
的I标志位置位,因为RAM_ADDR也是映射在SRAM空间,为了加快速度,编译器可能会编译出这样的代码:把RAM_ADDR读取到Register中,然后不停地判断Register相应位。而不会再读取RAM_ADDR,这样当然是不行了,因为程序或其它事件(中断等)会改变RAM_ADDR,结果很可能是一个死循环出不来了。如果定义成volatile型变量,编译的代码是这样的:每次要操作一个变量的时候都从内存中读取一次。
#define rGPACON(*(volatile unsigned long *)0x56000000)
对于不同的计算机体系结构,设备可能是端口映射,也可能是内存映射的。如果系统结构支持独立的IO
地址空间,并且是端口映射,就必须使用汇编语言完成实际对设备的控制,因为C语言并没有提供真正的“端口”的概念。如果是内存映射,那就方便的多了。
举个例子,比如像寄存器A
(地址假定为0x48000000)写入数据0x01,那么就可以这样设置了。
#define A (*(volatile unsigned long *)0x48000000)
...
     A = 0x01;
...
   
这实际上就是内存映射机制的方便性了。其中volatile关键字是嵌入式系统开发的一个重要特点。上述表达式拆开来分析,首先(volatile unsigned long *)0x48000000的意思是把0x48000000强制转换成volatile unsigned long类型的指针,暂记为p,那么就是#define A  *p,即A为P指针指向位置的内容了。这里就是通过内存寻址访问到寄存器A,可以读/写操作。
用GCC
编译时。volatile所指示的寄存器
不进行优化!!!


理解#define rRTCCON   (*(volatile unsigned char *)0x57000043) //RTC control
嵌入式系统编程,要求程序员能够利用C
语言访问固定的内存地址。既然是个地址,那么按照C语言的语法规则,这个表示地址的量应该是指针类型。所以,知道要访问的内存地址后,比如0x57000043
   第一步是要把它强制转换为指针类型
(unsigned char *)0x57000043
,s3c2410的rRTCCON是单字节访问的(怎么看出来的???我无法理解),所以0x57000043强制转换为指向unsignedchar
类型。
   volatile
(可变的)这个关键字说明这变量可能会被意想不到地改变,这样编译器就不会去假设这个变量的值了。这种“意想不到地改变”,不是由程序去改变,而是由硬件去改变——意想不到。
   第二步,对指针变量解引用,就能操作指针所指向的地址的内容了
   *(volatile unsigned char *)0x57000043
   第三步,小心地把#define
宏中的参数用括号括起来,这是一个很好的习惯。
在嵌入式系统中经常使用到Volatile,
对于volatile
的用法

编译器对代码的优化是指:
CPU
在执行的过程中,因为访问内存的速度远没有cpu的执行速度快,为了提高效率,引入了高速缓存cache. C编译器在编译时如果不知道变量会被其它外部因素(操作系统、硬件或者其它线程)修改,那么就会对该变量进行标识,即优化.那么这个变量在CPU的执行过程中,就会被放到高速缓存cache去,进而达到对变量的快速访问. 在了解了优化的概念后,试想如果我们事先就知道该变量会被外部因素改变,那么我们就在这个变量定义前加上Volatile,这样编译器就不会对该变量进行优化.这样该变量在cpu处理的过程当中,就不会被放到高速缓存cache
中。
为什么要让变量在执行的过程中不被放到cache
中去呢?
如果变量是被外部因素改变,
那么cpu就无法判断出这个变量已经被改变,那么程序在执行的过程中如果使用到该变量,还会继续使用cache中的变量,但是这个变量其实已经被改变了.
需要到内存地址中更新其内容了.
还有一个原因,
在一些寄存器变量或数据端口的使用中,因为寄存器变量本身也是靠cache来处理,为了避免引起错误,也可以使用volatile
修饰符.

简单的说使用volatile
的目的就是:
让对volatile
变量的存取不能缓存到寄存器,每次使用时需要重新存取。

 

<think>好的,我现在需要帮助用户理解C语言中的宏定义代码`(*(unsigned char volatile xdata *)地址)`的含义和用途。根据用户提供的引用内容,特别是引用[2]和[3],以及我之前关于位操作的回答,我需要逐步解析这个宏定义的结构和作用。 首先,用户引用的内容提到了Keil C51库中的宏定义,比如`XBYTE`,这些宏用于绝对地址访问。用户给出的例子`(*(unsigned char volatile xdata *)地址)`看起来类似这些宏的结构,需要拆解每个部分。 我需要先分解这个宏定义的各个部分: 1. `unsigned char`:这应该是指数据类型,8位无符号字符。 2. `volatile`:关键字,告诉编译器不要优化此变量,因为它可能被外部修改。 3. `xdata`:在51单片机中,xdata指外部RAM存储区,地址范围更大。 4. `*`:指针运算符,将地址转换为指针。 5. 最外层的`*(...)`:解引用指针,访问该地址的内容。 接下来,结合用户提供的引用[2]中的例子`#define IOPIN (*((volatile unsigned long *) 0xE0028000))`,这里也是类似的模式,将地址强制转换为指针类型,然后解引用。引用[3]提到`XBYTE`相当于指向外部数据区的指针,可以通过数组或指针运算访问特定地址。 用户可能想知道这个宏的具体作用,比如如何通过它访问硬件寄存器或特定内存地址。需要解释每个关键字的作用,尤其是`volatile`的重要性,防止编译器优化导致意外行为。同时,`xdata`在51架构中的意义,区分不同的存储区域(data, idata, xdata等)。 可能还需要对比其他宏定义,如引用[1]中的`XBYTE`,说明用户给出的宏与标准库中的宏有何异同。例如,`XBYTE`定义的是基地址0,通过数组索引访问,而用户的例子是直接指定地址,可能需要通过宏参数传入地址值。 另外,用户之前的问题涉及到位操作符`&=`和`|=`,这里可能需要连接起来,说明在底层编程中,如何通过宏访问特定地址,然后使用位操作来设置或清除寄存器位。例如,通过类似`*(volatile unsigned char*)0x3000 |= 0x01;`来设置某个寄存器的位。 需要注意用户可能的应用场景,比如嵌入式开发中的硬件寄存器操作,或者访问外部存储器。需要强调这种直接地址访问在系统级编程中的常见用途和注意事项,如确保地址正确性,避免内存冲突等。 最后,要确保解释清晰,避免技术术语过于复杂,同时覆盖用户可能存在的疑问点,比如volatile的作用、指针转换的意义、存储空间修饰符的重要性等。结合用户提供的引用内容,确保回答准确且有依据。</think>在C语言中,宏定义`(*(unsigned char volatile xdata *)地址)`是**直接访问特定物理地址的经典写法**,尤其在嵌入式开发中用于操作硬件寄存器或访问特定内存区域。以下是其详细解析: --- ### 一、语法拆解 #### 1. **`unsigned char`** - **作用**:声明操作的数据类型为**无符号8位字符**(1字节)。 - **意义**:表示访问的目标地址存储的是单字节数据,例如外设寄存器的8位状态值。 #### 2. **`volatile`** - **作用**:禁止编译器优化对此地址的访问[^2]。 - **必要性**:硬件寄存器或共享内存的值可能被外部修改(如中断、硬件触发),`volatile`确保每次访问都直接从内存读取最新值,而非使用缓存值。 #### 3. **`xdata`** - **作用**:指定存储空间类型(针对8051架构)[^1]。 - **意义**: - 在51单片机中,`xdata`表示**外部扩展RAM**(地址范围0x0000-0xFFFF),需通过MOVX指令访问。 - 其他存储类型如`data`(片内RAM)、`code`(程序存储器)也有类似用法。 #### 4. **`(unsigned char volatile xdata *)`** - **作用**:将数值地址强制转换为**指向xdata空间的无符号字符指针**。 - **示例**: 若地址为`0x3000`,则`(unsigned char volatile xdata *)0x3000`生成一个指向外部RAM 0x3000地址的指针。 #### 5. **最外层的`*`** - **作用**:**解引用指针**,实现对目标地址的直接读写。 - **完整意义**: `*(指针)`表示访问指针指向的地址内容,等同于直接操作该物理地址。 --- ### 二、实际用途 #### 1. **访问硬件寄存器** 在嵌入式系统中,外设寄存器(如GPIO、UART)的地址是固定的。通过此宏可直接读写寄存器: ```c // 示例:向地址0x3000写入值0xFF *(unsigned char volatile xdata *)0x3000 = 0xFF; // 写操作 unsigned char val = *(unsigned char volatile xdata *)0x3000; // 读操作 ``` #### 2. **替代Keil C51标准库的XBYTE** 引用[1]中的`XBYTE`宏定义: ```c #define XBYTE ((unsigned char volatile xdata *) 0) ``` - 用户代码`*(unsigned char volatile xdata *)地址`相当于直接扩展了`XBYTE[地址]`的功能[^3]。 - 区别:`XBYTE`基地址为0,需通过索引访问(如`XBYTE[0x3000]`),而用户代码直接指定任意地址。 --- ### 三、关键设计思想 #### 1. **地址强制转换** - **原理**:C语言允许将整数直接转换为指针,通过指针操作访问物理地址。 - **语法规则**:`(目标类型)地址`将数值转换为特定类型的指针,例如: ```c int *p = (int *)0x4000; // 将0x4000转换为int型指针 ``` #### 2. **volatile的必要性** - **避免优化陷阱**:若未使用`volatile`,编译器可能合并重复的读操作或删除“冗余”写操作,导致硬件交互错误。 - **典型场景**:轮询状态寄存器时,必须用`volatile`确保每次读取真实值: ```c while (*(volatile unsigned char *)0x3000 & 0x80); // 等待最高位变为0 ``` #### 3. **存储空间修饰符(xdata)** - **硬件相关性**:在51架构中,不同存储空间(data/xdata/code)的访问方式不同,需通过修饰符告知编译器生成正确的指令(如MOVX)。 --- ### 四、对比其他写法 #### 1. **结构体映射寄存器组** 引用[4]提到,通过结构体管理连续寄存器更高效: ```c typedef struct { volatile unsigned char CTRL; // 控制寄存器 volatile unsigned char STATUS;// 状态寄存器 } UART_TypeDef; #define UART0 ((UART_TypeDef *)0x3000) UART0->CTRL = 0x01; // 访问0x3000地址 ``` - **优势**:代码可读性高,适合管理连续多寄存器。 #### 2. **宏定义的简化** 可封装为通用宏,提升代码复用性: ```c #define REG(addr) (*(unsigned char volatile xdata *)(addr)) REG(0x3000) = 0xFF; // 等效于原写法 ``` --- ### 五、应用场景 1. **硬件初始化**:配置外设寄存器(如设置GPIO方向)。 2. **中断服务程序**:读写状态寄存器清除中断标志。 3. **内存映射设备**:操作外部扩展RAM或FPGA寄存器。 --- ### 六、注意事项 1. **地址合法性**:确保访问的地址在硬件允许范围内,否则可能导致程序崩溃或硬件损坏。 2. **对齐问题**:某些架构要求地址对齐(如32位系统需4字节对齐),需注意数据类型匹配。 3. **跨平台限制**:此写法高度依赖硬件和编译器,在非嵌入式环境(如PC)中可能无效。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值