找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 5994|回复: 1
收起左侧

STM32中*(int *)0x08001000写法理解

[复制链接]
ID:90762 发表于 2018-10-22 23:05 | 显示全部楼层 |阅读模式
关于STM32中*(int*)0x08001000写法理解

它与(*(volatile unsignedlong *)概念是一样。请看51黑电子论坛嵌入式的C程序中(*(volatileunsigned long *)的理解文章。


注意:1.它与(int*)0x08001000 少了一*,是完全不同概念

2.在32位机正确,在16位机不一定正确。

*(int *)0x80001000 = 34; 与(int)0x80001000是等效


uint32GPIOx
说实在的,这个,和 那个显得很麻烦的写法
*(uint32_t*)&GPIOx
效果确实是一样的。

在STM32上,可以通过
*(int *)0x08001000 = 34;
在地址为 0x80010000上写入 34 这个内容。

以下写是理解,是一些其它论坛好文章。

——这里不考虑什么 FLASH RAM之类的问题。

所以,ST库映射寄存器的典型手法就是
比如说 GPIOA的寄存器地址,如果是从 0x20001000开始存放什么 ODR IDR之类的。
因为 结构体成员在内存上也是按序排放的,所以,它就把
ODR IDR等等寄存器 按顺序 定义成 GPIO 这个结构体。
形如
typedef GPIO
{
    ODR;
    IDR;
};
这一部分具体可以去看stm32fxxx.h,我就不多说了。
最后,GPIOA GPIOBGPIOC都会有一个GPIOA_Base GPIOB_Base
这个基地址,指的就是 每个port端口寄存器的 起始地址。ABCDEFGI口各自按顺序排好。
所以只要找到头,再借助这个结构体,就可以直接通过
GPIOA->ODR这样的写法非常简单直观的寻知道 GPIOA的ODR地址。
非常形象,非常生动。
而且很简洁,完全的利用了C语法本身的特性。
是以,从我个人的角度看,这是一个非常不错的 映射手法。
这基本也是那天晚上我语无伦次 发语音说的重点。
现在,先来回答原来那个帖子的问题。
*(uint32_t*)&GPIOx
这句话到底是什么意思?
其实问题还是在上一个帖子里提到的 GPIOx的定义里
#define GPIOA    ((GPIO_TypeDef*)GPIOA_BASE)
其实我为什么第一反应会觉得这个东西写的挺新鲜,因为以前我曾小小纠结过如何通过一个函数,让函数自己区分 GPIOA GPIOB这个问题。
而这里提供了一个 非常直接简单的方法:

*(uint32_t*)&  GPIOx
对这种较复杂的表达式或者宏,解决的思路很简单,就是一步一步展开。但这个过于简单,而且这个话题也太口水了,我就直接带过去不罗嗦了,罗嗦了你们还以为是我无知大惊小怪......(多怨啊我,我只是一个喜欢 详细解释的好版主)
GPIOx 是传递进来的形参,它的可能值就是 GPIOA GPIOB之类的
那也就是
((GPIO_TypeDef*GPIOA_BASE
GPIOA_BASE是一个数值,代表的是 GPIOA的寄存器的起始地址
*(uint32_t*)&GPIOx
这个操作,等于,把GPIOA_BASE 这个最初宏定义的数值,就是说,这是一个常数。
所以这个时候,就可以很方便的使用 switch-case结构了
因为case后面跟的只能是常数,而不能是变量或者其他任何数值。
这就是这个问题的所有答案
只是,我强调 后面这种写法,我的理由在于:
可读性。
看到前者,你不会联想到 GPIOx是一个地址,而看到后者,稍微有点经验的C程序员都马上会领悟到这一点。
这就很重要。
为什么,因为,在类似的环境下,我就会搞懵。
比如说,最开始主楼贴 的那个图。
那是 人民币君发的。
我一开始因为直接联想到 这个放假前的讨论,因此我想都没想,就直接说,这两个效果是一样的。
然而,果真是一样吗?呵呵,那还真不是。
比如说
0.png
{
*(int *)0x80001000 = 34;
(int)0x80001000
while(1);
}
写到这里,我就懵逼了,看出问题了没?
一个是那个数其实是地址值,要去操作那个地址上的内容
另一个,压根就只是一个常数。
这两个操作的出来的结果和影响完全不一样。
-----------------------------------------------------------------------
首先来讨论一个我认为很有意思的问题,就是这两个强制类型转换:
*(uint32_t*)&AAA 是否等价于 (uint32_t)AAA ?(假设我们不知道AAA的类型,变量还是常量)

为了便于讨论,我们假设变量uint32_tX=*(uint32_t*)&AAA, Y=(uint32_t)AAA;

乍一看,好像是有点等价的意思,但是仔细想想,又不是那么回事,这还取决于AAA的类型。
(1).现在假设AAA是2字节short型=0x1234;
那么X的结果是强制从AAA的地址中取走4字节,其中2字节未知:
X=0xXXXX1234 (小端情况下)
Y的结果就比较确定,编译器帮他把高位填0, Y=0x00001234;
(2).假设AAA是64位long long类型=0x1234;前面的0我就不写了。
那么X还是取走4个字节,根据大小端而异,可能是高4字节,也可能是低四字节。
Y只是简单的舍弃了高四字节,结果比较确定。
(3). 假设AAA是个数组,这个情况比较特殊,数组名本身就是数组的首地址,取地址后还是相同的值:
因此X会取出数组中的元素
而Y却还是一个地址值。
(4). 一个不靠谱的假设AAA是个结构体
那么X可以取到结构体的成员
而Y的写法就直接报错了,不允许强制类型转换。
(5). AAA是uint32_t类型,那么没什么问题,X与Y等价。

由此可见:
X写法确实是无条件强制转换,基本是无所不能转,但是如果类型不匹配就很容出错了。
Y写法是有限制的编译器参与的半智能转换,因为编译器知道源类型与目标类型,会帮忙参与转换,或者类型转换不太靠谱的话,直接报错。
由此可以得出结论,在使用强制类型转换的时候,必须具有可行性,同时也必须清楚转换后的结果,也就是说,程序员(写程序的人)必须清清楚楚地知道源类型和目标类型到底是什么,否则还是去读书深造的好。

现在我来说说为什么我觉得uint32_tX=*(uint32_t*)&GPIOx有脱裤子放屁的嫌疑(从阅读程序的角度来说)。

首先有个变量GPIOx,是个指针,也就是个地址1,此地址上面存放的类型是GPIO_TypeDef。
然后对这个地址1取了个地址2,那么这个地址2的类型是GPIO_TypeDef**
现在对地址2进行强制类型转换,转成uint32_t*,也就是间接说明,不再把地址1当做地址(指针)看待,而是作为一个uint32_t类型。
然后在地址2中的uint32_t数据取出来,完毕。牛逼的程序员也看出来了,这就是拐外抹角的把GPIOx转成uint32_t类型。
一般刚入门的程序员看了会不会很懵逼?
好,说的有点乱,我们按教主的思路重新捋一下:
我们假设不知道GPIOx到底是个什么东西,就当做是一个不知道类型的普通变量。
引用:“——————————————————————————————————
只是,我强调 后面这种写法,我的理由在于:
可读性。
看到前者,你不会联想到GPIOx是一个地址,而看到后者,稍微有点经验的C程序员都马上会领悟到这一点。
————————————————————————————————引用结束”
首先一个变量GPIOx,不知道其类型
然后对此变量取了个地址,此地址类型也未知。
然后对这个地址进行强制转换成uint32_t类型的一个地址(此处影射GPIOx是个uint32_t类型)
最后,从这个地址中取出了一个uint32_t类型的变量,完成了最终这个语句的使命。
这样理解,也根本看不出GPIOx有地址的意思(只是明确了变量的地址是个具体类型的地址)。只是拐了个弯,把GPIOx强制转成uint32_t类型而已。

然而,把一个地址(指针)转成一个整形数,就很常见不过了。uint32_tY=(uint32_t)GPIOx。

由此,可以看到两个转换的最终区别:脱裤子那种,是强制转换的变量地址的类型,间接对数据类型进行转换,而直接转换就是直接对变量进行转换。
-------------------------------------------------------------
注意:
32位机器下,你是对的,你还是更简洁的。
然而如果这种写法,在16位机或者64位机 等非32位机下就会出错
why?
很简单,,非32位机的 地址非4字节,而是 16位机的2字节,64位机的8字节。
这种情况下,对应的指针字长也就成了 2字节 8字节。
于是结果已经很明显。
你每次都uint32去强转地址,问题是,你强转的是一个地址.......那也就是说。
对16位机,你多转了后面未知的2字节,对64位机,你少转了后面需要的4字节。
所以,必然是错的。
而原来那种看起来复杂的写法呢?
木有错,为毛?
因为,,uint32_t *也是一个指针,或者地址(指针或地址随你叫吧)
因为在同一机器下,任何类型指针的字长都是一样的。
所以这种情况下,我读到的地址值永远不会少或者多。
这个问题意味着。
在你的写法里,你需要去假设指针字长,比如uint32,但这永远只能对一种机器字长适应。
而那个复杂的写法,则无此需求,不需要作任何假设,也就不会受限于任何机器字长的限制。
事实上,我写成
*(uint8_t *)
*(uint16_t *)
.....
都木有任何关系
-----------------------------------------------
(uint32) GPIOx  还是  *(uint32_t*)&GPIOx
如果GPIOx是形参,无论哪种写法,得出的汇编都一样,都是直接取R0,
如果GPIOx 是全局变量,汇编结果也是一样,都是取GPIOx变量的内容
如果GPIOx变量存放到0x10001000,直接取0x10001000里面的内容
编译器非常聪明,认为对指针变量X取地址A再取A里面的内容和直接取X里面的内容是一样的!
----------------------------------
C语言在程序移植这里确实存在许多诟病,在不同硬件平台上,数据类型的长度并不统一。
例如通常int在16位机为2字节,32位机为4字节,指针也一样,根据机型有2字节,4字节或者8字节的长度,记得还有3字节的……因为硬件平台种类实在是太多了,五花八门。
为了应付这类问题,C99标准出台了更具体的类型,如int16_t,uint32_t这样的具体长度类型,使程序在不同硬件平台更容易移植。但是……。
遗憾的是,这些类型中没有指针,我觉得因为指针也没法具体化。因此,教主提出了他的问题:在不同平台上如何用整型来表示指针?
大家都应该知道,指针是有类型的,解引用的时候会得到相应的类型:
*(uint32_t*)xxx  结果将是uint32_t
*(int16_t*)xxx 结果就是int16_t
根本得不到他所想要的与平台相关的数据类型。
因此在C语言中,想要得到指针所对应的整型类型,只能通过手动指定,例如微软就是这么干的
#ifdef _WIN64
  typedef __int64         intptr_t;
#else
  typedef int             intptr_t;
#endif
这样intptr_t就可以确保能保存指针类型。
但是可惜这只是某些厂家这么干,ST的库中并没有这样的类型,否则这事就好办了(当然了,ST目前可能也没考虑推出64位的单片机)
我不知道*(uint32_t*)&GPIOx这样的代码是否是出自ST的标准库,我没有去考证,如果真这样写的话他们自己可能也看着别扭,所以我看到st的某个版本库里面看到的是直接比较指针,当然了,就不能使用switch语句了,而是if语句,像这样:
if (GPIOx == GPIOA) xxx_statement 。反正我觉得这样写是最直观最易读的了,给他们点个赞!
c语言的争议太多了,就像#define与typedef之争,#define与const之争,程序员的理论就是运行结果没错那就都不是大问题。


完整的Word格式文档51黑下载地址:
STM32中理解.zip (57.89 KB, 下载次数: 11)

评分

参与人数 1黑币 +100 收起 理由
admin + 100 共享资料的黑币奖励!

查看全部评分

回复

使用道具 举报

ID:258566 发表于 2018-10-23 11:40 | 显示全部楼层
绘图.png
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表