资讯详情

C语言中地址与指针的强制转换

📅 2026/10/24 5:41:31 | 华诺云谱 👁 阅读
C语言中地址与指针的强制转换
我们知道给寄存器地址进行命名的过程叫做寄存器映射。映射只是把编号分配好但编号对应的地方不一定都有物理存储。那么各位伙伴是否知道*(unsigned int*)(0x4001 080C)代表什么意思首先寄存器地址分为三个部分总线基地址。外设基于总线地址的偏移量。寄存器相对于外设基地址的偏移量。flowchart TD A[总线基地址br/0x4000 0000] -- B[ 外设偏移量br/0x0001 0800] B -- C[ GPIOA 外设基地址br/0x4001 0800] C -- D[ 寄存器偏移量br/0x0000 000C] D -- E[ GPIOA_ODR 寄存器地址br/0x4001 080C]上图展示了 GPIOA_ODR 寄存器地址的推导过程总线基地址0x4000 0000加上外设偏移量0x0001 0800得到 GPIOA 外设基地址0x4001 0800再加上寄存器偏移量0x0000 000C最终得到0x4001 080C。下面以 STM32 的 GPIOA 为例把这三部分地址实际相加一次看看0x4001 080C是怎么来的。地址组成数值含义总线基地址0x4000 0000APB2 外设总线基地址STM32 中 GPIOA 挂载在 APB2 总线上外设基于总线地址的偏移量0x0001 0800GPIOA 外设相对于 APB2 总线基地址的偏移量寄存器相对于外设基地址的偏移量0x0000 000CGPIOA_ODR输出数据寄存器相对于 GPIOA 外设基地址的偏移量寄存器物理地址0x4001 080C三段地址相加的结果即 GPIOA_ODR 寄存器的实际地址把三段地址按十六进制逐位相加0x4000 0000 0x0001 0800 0x0000 000C 0x4001 080C。也就是说GPIOA 外设的基地址是0x4001 0800而 ODR 寄存器在 GPIOA 内部又偏移了0x0C最终得到0x4001 080C。这个地址就是后面*(unsigned int*)(0x4001 080C)中那个十六进制常数的来源。在*(unsigned int*)(0x4001 080C)中0x4001 080C是一个十六进制常数。外设寄存器的物理地址总线基地址 外设基于总线地址的偏移量 寄存器相对于外设基地址的偏移量本身是一个数字并不是指针。(unsigned int*)(0x4001 080C)的含义是把该数字转换成一个无符号 int 型指针也就是声明这个地址里存放的数据类型是 unsigned int32 位。注意在这个过程中并没有开辟内存空间来存放这个地址编译器也没有做任何内存读取或硬件查询只是完成了类型标记替换绑定了一套访问规则即要取这个地址上无符号 4 字节的值因为硬件地址本质上只是比特流。最后*(unsigned int*)(0x4001 080C)中的*表示指针解引用。编译器会生成 LDR 和 STR读数据和写数据的指令等汇编指令按下图的操作顺序生成可执行程序代表 CPU 取这个指针指向的地址读取该地址上 32 bit 的值。编译器把 C 语言代码编译成汇编指令确定了指针类型、解引用语义和访问宽度。汇编器把汇编指令翻译成机器码。链接器把多个目标文件合并成可执行程序。程序加载到 Flash 或内存后CPU 取指令、译码、执行。指针变量存储的是内存地址其类型决定了如何解释该地址所指向的数据。不同类型的指针在解引用时会按照对应的数据大小进行操作。例如int*指向一个整型数据访问时按 4 字节处理char*则按 1 字节处理。使用(type*)语法可将任意指针转换为另一种指针类型。例如(int*)p将void*指针转换为int*。这种转换不改变底层地址值仅改变编译器对指针行为的解释方式。指针类型的本质规定了访问的规则。访问一次取多少字节。指针自增和自减时地址跳多少。取出数据后怎么把这一串二进制比特翻译成数值。最后补充一点类型语义解释发生在编译期地址访问发生在 CPU 执行期。下面用一段 C 语言代码把前面的地址推导和指针语义串起来用宏定义 GPIOA_ODR 的寄存器地址再通过*(unsigned int*)(0x4001 080C)完成读和写。#include stdio.h /* 1. 总线基地址APB2 外设总线基地址 */ #define PERIPH_BASE 0x40000000UL /* 2. 外设偏移量GPIOA 相对于 APB2 总线基地址的偏移 */ #define APB2_GPIOA_OFFSET 0x00010800UL /* GPIOA 外设基地址 总线基地址 外设偏移量 0x4001 0800 */ #define GPIOA_BASE (PERIPH_BASE APB2_GPIOA_OFFSET) /* 3. 寄存器偏移量ODR 相对于 GPIOA 外设基地址的偏移 0x0C */ #define GPIOA_ODR_OFFSET 0x0CUL /* 寄存器物理地址 0x4001 0800 0x0C 0x4001 080C */ #define GPIOA_ODR_ADDR (GPIOA_BASE GPIOA_ODR_OFFSET) /* 把地址常量转换成 unsigned int* 指针再解引用得到可读写的 32 位寄存器 */ #define GPIOA_ODR (*(unsigned int *)(GPIOA_ODR_ADDR)) int main(void) { unsigned int value; /* 读CPU 从 0x4001 080C 取 32 bit 数据对应 LDR 指令 */ value GPIOA_ODR; printf(GPIOA_ODR 0x%08X\n, value); /* 写把第 5 位置 1其余位保持不变对应 STR 指令 */ /* 1UL 5 0x0000 0020即 PA5 输出高电平 */ GPIOA_ODR | (1UL 5); /* 写把第 5 位清 0其余位保持不变即 PA5 输出低电平 */ /* ~(1UL 5) 0xFFFF FFDF按位与后仅第 5 位被清零 */ GPIOA_ODR ~(1UL 5); /* 写整体赋值一次性改写 ODR 的全部 16 个输出位 */ GPIOA_ODR 0x00000020UL; return 0; }代码中的每一步都能对应回前面的地址推导PERIPH_BASE是总线基地址APB2_GPIOA_OFFSET是外设偏移量GPIOA_ODR_OFFSET是寄存器偏移量三者相加得到0x4001 080C。而(unsigned int *)只负责告诉编译器“按 4 字节无符号整数访问”真正的读写动作发生在 CPU 执行LDR和STR指令时。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑