找回密碼
 立即注冊(cè)

QQ登錄

只需一步,快速開始

帖子
查看: 7298|回復(fù): 1
收起左側(cè)

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

[復(fù)制鏈接]
ID:90762 發(fā)表于 2018-10-22 23:05 | 顯示全部樓層 |閱讀模式
關(guān)于STM32中*(int*)0x08001000寫法理解

它與(*(volatile unsignedlong *)概念是一樣。請(qǐng)看51黑電子論壇嵌入式的C程序中(*(volatileunsigned long *)的理解文章。


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

2.在32位機(jī)正確,在16位機(jī)不一定正確。

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


uint32GPIOx
說實(shí)在的,這個(gè),和 那個(gè)顯得很麻煩的寫法
*(uint32_t*)&GPIOx
效果確實(shí)是一樣的。

在STM32上,可以通過
*(int *)0x08001000 = 34;
在地址為 0x80010000上寫入 34 這個(gè)內(nèi)容。

以下寫是理解,是一些其它論壇好文章。

——這里不考慮什么 FLASH RAM之類的問題。

所以,ST庫映射寄存器的典型手法就是
比如說 GPIOA的寄存器地址,如果是從 0x20001000開始存放什么 ODR IDR之類的。
因?yàn)?結(jié)構(gòu)體成員在內(nèi)存上也是按序排放的,所以,它就把
ODR IDR等等寄存器 按順序 定義成 GPIO 這個(gè)結(jié)構(gòu)體。
形如
typedef GPIO
{
    ODR;
    IDR;
};
這一部分具體可以去看stm32fxxx.h,我就不多說了。
最后,GPIOA GPIOBGPIOC都會(huì)有一個(gè)GPIOA_Base GPIOB_Base
這個(gè)基地址,指的就是 每個(gè)port端口寄存器的 起始地址。ABCDEFGI口各自按順序排好。
所以只要找到頭,再借助這個(gè)結(jié)構(gòu)體,就可以直接通過
GPIOA->ODR這樣的寫法非常簡(jiǎn)單直觀的尋知道 GPIOA的ODR地址。
非常形象,非常生動(dòng)。
而且很簡(jiǎn)潔,完全的利用了C語法本身的特性。
是以,從我個(gè)人的角度看,這是一個(gè)非常不錯(cuò)的 映射手法。
這基本也是那天晚上我語無倫次 發(fā)語音說的重點(diǎn)。
現(xiàn)在,先來回答原來那個(gè)帖子的問題。
*(uint32_t*)&GPIOx
這句話到底是什么意思?
其實(shí)問題還是在上一個(gè)帖子里提到的 GPIOx的定義里
#define GPIOA    ((GPIO_TypeDef*)GPIOA_BASE)
其實(shí)我為什么第一反應(yīng)會(huì)覺得這個(gè)東西寫的挺新鮮,因?yàn)橐郧拔以⌒〖m結(jié)過如何通過一個(gè)函數(shù),讓函數(shù)自己區(qū)分 GPIOA GPIOB這個(gè)問題。
而這里提供了一個(gè) 非常直接簡(jiǎn)單的方法:

*(uint32_t*)&  GPIOx
對(duì)這種較復(fù)雜的表達(dá)式或者宏,解決的思路很簡(jiǎn)單,就是一步一步展開。但這個(gè)過于簡(jiǎn)單,而且這個(gè)話題也太口水了,我就直接帶過去不羅嗦了,羅嗦了你們還以為是我無知大驚小怪......(多怨啊我,我只是一個(gè)喜歡 詳細(xì)解釋的好版主)
GPIOx 是傳遞進(jìn)來的形參,它的可能值就是 GPIOA GPIOB之類的
那也就是
((GPIO_TypeDef*GPIOA_BASE
GPIOA_BASE是一個(gè)數(shù)值,代表的是 GPIOA的寄存器的起始地址
*(uint32_t*)&GPIOx
這個(gè)操作,等于,把GPIOA_BASE 這個(gè)最初宏定義的數(shù)值,就是說,這是一個(gè)常數(shù)。
所以這個(gè)時(shí)候,就可以很方便的使用 switch-case結(jié)構(gòu)了
因?yàn)閏ase后面跟的只能是常數(shù),而不能是變量或者其他任何數(shù)值。
這就是這個(gè)問題的所有答案
只是,我強(qiáng)調(diào) 后面這種寫法,我的理由在于:
可讀性。
看到前者,你不會(huì)聯(lián)想到 GPIOx是一個(gè)地址,而看到后者,稍微有點(diǎn)經(jīng)驗(yàn)的C程序員都馬上會(huì)領(lǐng)悟到這一點(diǎn)。
這就很重要。
為什么,因?yàn)�,在類似的環(huán)境下,我就會(huì)搞懵。
比如說,最開始主樓貼 的那個(gè)圖。
那是 人民幣君發(fā)的。
我一開始因?yàn)橹苯勇?lián)想到 這個(gè)放假前的討論,因此我想都沒想,就直接說,這兩個(gè)效果是一樣的。
然而,果真是一樣嗎?呵呵,那還真不是。
比如說
0.png
{
*(int *)0x80001000 = 34;
(int)0x80001000
while(1);
}
寫到這里,我就懵逼了,看出問題了沒?
一個(gè)是那個(gè)數(shù)其實(shí)是地址值,要去操作那個(gè)地址上的內(nèi)容
另一個(gè),壓根就只是一個(gè)常數(shù)。
這兩個(gè)操作的出來的結(jié)果和影響完全不一樣。
-----------------------------------------------------------------------
首先來討論一個(gè)我認(rèn)為很有意思的問題,就是這兩個(gè)強(qiáng)制類型轉(zhuǎn)換:
*(uint32_t*)&AAA 是否等價(jià)于 (uint32_t)AAA ?(假設(shè)我們不知道AAA的類型,變量還是常量)

為了便于討論,我們假設(shè)變量uint32_tX=*(uint32_t*)&AAA, Y=(uint32_t)AAA;

乍一看,好像是有點(diǎn)等價(jià)的意思,但是仔細(xì)想想,又不是那么回事,這還取決于AAA的類型。
(1).現(xiàn)在假設(shè)AAA是2字節(jié)short型=0x1234;
那么X的結(jié)果是強(qiáng)制從AAA的地址中取走4字節(jié),其中2字節(jié)未知:
X=0xXXXX1234 (小端情況下)
Y的結(jié)果就比較確定,編譯器幫他把高位填0, Y=0x00001234;
(2).假設(shè)AAA是64位long long類型=0x1234;前面的0我就不寫了。
那么X還是取走4個(gè)字節(jié),根據(jù)大小端而異,可能是高4字節(jié),也可能是低四字節(jié)。
Y只是簡(jiǎn)單的舍棄了高四字節(jié),結(jié)果比較確定。
(3). 假設(shè)AAA是個(gè)數(shù)組,這個(gè)情況比較特殊,數(shù)組名本身就是數(shù)組的首地址,取地址后還是相同的值:
因此X會(huì)取出數(shù)組中的元素
而Y卻還是一個(gè)地址值。
(4). 一個(gè)不靠譜的假設(shè)AAA是個(gè)結(jié)構(gòu)體
那么X可以取到結(jié)構(gòu)體的成員
而Y的寫法就直接報(bào)錯(cuò)了,不允許強(qiáng)制類型轉(zhuǎn)換。
(5). AAA是uint32_t類型,那么沒什么問題,X與Y等價(jià)。

由此可見:
X寫法確實(shí)是無條件強(qiáng)制轉(zhuǎn)換,基本是無所不能轉(zhuǎn),但是如果類型不匹配就很容出錯(cuò)了。
Y寫法是有限制的編譯器參與的半智能轉(zhuǎn)換,因?yàn)榫幾g器知道源類型與目標(biāo)類型,會(huì)幫忙參與轉(zhuǎn)換,或者類型轉(zhuǎn)換不太靠譜的話,直接報(bào)錯(cuò)。
由此可以得出結(jié)論,在使用強(qiáng)制類型轉(zhuǎn)換的時(shí)候,必須具有可行性,同時(shí)也必須清楚轉(zhuǎn)換后的結(jié)果,也就是說,程序員(寫程序的人)必須清清楚楚地知道源類型和目標(biāo)類型到底是什么,否則還是去讀書深造的好。

現(xiàn)在我來說說為什么我覺得uint32_tX=*(uint32_t*)&GPIOx有脫褲子放屁的嫌疑(從閱讀程序的角度來說)。

首先有個(gè)變量GPIOx,是個(gè)指針,也就是個(gè)地址1,此地址上面存放的類型是GPIO_TypeDef。
然后對(duì)這個(gè)地址1取了個(gè)地址2,那么這個(gè)地址2的類型是GPIO_TypeDef**
現(xiàn)在對(duì)地址2進(jìn)行強(qiáng)制類型轉(zhuǎn)換,轉(zhuǎn)成uint32_t*,也就是間接說明,不再把地址1當(dāng)做地址(指針)看待,而是作為一個(gè)uint32_t類型。
然后在地址2中的uint32_t數(shù)據(jù)取出來,完畢。牛逼的程序員也看出來了,這就是拐外抹角的把GPIOx轉(zhuǎn)成uint32_t類型。
一般剛?cè)腴T的程序員看了會(huì)不會(huì)很懵逼?
好,說的有點(diǎn)亂,我們按教主的思路重新捋一下:
我們假設(shè)不知道GPIOx到底是個(gè)什么東西,就當(dāng)做是一個(gè)不知道類型的普通變量。
引用:“——————————————————————————————————
只是,我強(qiáng)調(diào) 后面這種寫法,我的理由在于:
可讀性。
看到前者,你不會(huì)聯(lián)想到GPIOx是一個(gè)地址,而看到后者,稍微有點(diǎn)經(jīng)驗(yàn)的C程序員都馬上會(huì)領(lǐng)悟到這一點(diǎn)。
————————————————————————————————引用結(jié)束”
首先一個(gè)變量GPIOx,不知道其類型
然后對(duì)此變量取了個(gè)地址,此地址類型也未知。
然后對(duì)這個(gè)地址進(jìn)行強(qiáng)制轉(zhuǎn)換成uint32_t類型的一個(gè)地址(此處影射GPIOx是個(gè)uint32_t類型)
最后,從這個(gè)地址中取出了一個(gè)uint32_t類型的變量,完成了最終這個(gè)語句的使命。
這樣理解,也根本看不出GPIOx有地址的意思(只是明確了變量的地址是個(gè)具體類型的地址)。只是拐了個(gè)彎,把GPIOx強(qiáng)制轉(zhuǎn)成uint32_t類型而已。

然而,把一個(gè)地址(指針)轉(zhuǎn)成一個(gè)整形數(shù),就很常見不過了。uint32_tY=(uint32_t)GPIOx。

由此,可以看到兩個(gè)轉(zhuǎn)換的最終區(qū)別:脫褲子那種,是強(qiáng)制轉(zhuǎn)換的變量地址的類型,間接對(duì)數(shù)據(jù)類型進(jìn)行轉(zhuǎn)換,而直接轉(zhuǎn)換就是直接對(duì)變量進(jìn)行轉(zhuǎn)換。
-------------------------------------------------------------
注意:
32位機(jī)器下,你是對(duì)的,你還是更簡(jiǎn)潔的。
然而如果這種寫法,在16位機(jī)或者64位機(jī) 等非32位機(jī)下就會(huì)出錯(cuò)。
why?
很簡(jiǎn)單,,非32位機(jī)的 地址非4字節(jié),而是 16位機(jī)的2字節(jié),64位機(jī)的8字節(jié)。
這種情況下,對(duì)應(yīng)的指針字長(zhǎng)也就成了 2字節(jié) 8字節(jié)。
于是結(jié)果已經(jīng)很明顯。
你每次都uint32去強(qiáng)轉(zhuǎn)地址,問題是,你強(qiáng)轉(zhuǎn)的是一個(gè)地址.......那也就是說。
對(duì)16位機(jī),你多轉(zhuǎn)了后面未知的2字節(jié),對(duì)64位機(jī),你少轉(zhuǎn)了后面需要的4字節(jié)。
所以,必然是錯(cuò)的。
而原來那種看起來復(fù)雜的寫法呢?
木有錯(cuò),為毛?
因?yàn)椋�,uint32_t *也是一個(gè)指針,或者地址(指針或地址隨你叫吧)
因?yàn)樵谕粰C(jī)器下,任何類型指針的字長(zhǎng)都是一樣的。
所以這種情況下,我讀到的地址值永遠(yuǎn)不會(huì)少或者多。
這個(gè)問題意味著。
在你的寫法里,你需要去假設(shè)指針字長(zhǎng),比如uint32,但這永遠(yuǎn)只能對(duì)一種機(jī)器字長(zhǎng)適應(yīng)。
而那個(gè)復(fù)雜的寫法,則無此需求,不需要作任何假設(shè),也就不會(huì)受限于任何機(jī)器字長(zhǎng)的限制。
事實(shí)上,我寫成
*(uint8_t *)
*(uint16_t *)
.....
都木有任何關(guān)系
-----------------------------------------------
(uint32) GPIOx  還是  *(uint32_t*)&GPIOx
如果GPIOx是形參,無論哪種寫法,得出的匯編都一樣,都是直接取R0,
如果GPIOx 是全局變量,匯編結(jié)果也是一樣,都是取GPIOx變量的內(nèi)容
如果GPIOx變量存放到0x10001000,直接取0x10001000里面的內(nèi)容
編譯器非常聰明,認(rèn)為對(duì)指針變量X取地址A再取A里面的內(nèi)容和直接取X里面的內(nèi)容是一樣的!
----------------------------------
C語言在程序移植這里確實(shí)存在許多詬病,在不同硬件平臺(tái)上,數(shù)據(jù)類型的長(zhǎng)度并不統(tǒng)一。
例如通常int在16位機(jī)為2字節(jié),32位機(jī)為4字節(jié),指針也一樣,根據(jù)機(jī)型有2字節(jié),4字節(jié)或者8字節(jié)的長(zhǎng)度,記得還有3字節(jié)的……因?yàn)橛布脚_(tái)種類實(shí)在是太多了,五花八門。
為了應(yīng)付這類問題,C99標(biāo)準(zhǔn)出臺(tái)了更具體的類型,如int16_t,uint32_t這樣的具體長(zhǎng)度類型,使程序在不同硬件平臺(tái)更容易移植。但是……。
遺憾的是,這些類型中沒有指針,我覺得因?yàn)橹羔樢矝]法具體化。因此,教主提出了他的問題:在不同平臺(tái)上如何用整型來表示指針?
大家都應(yīng)該知道,指針是有類型的,解引用的時(shí)候會(huì)得到相應(yīng)的類型:
*(uint32_t*)xxx  結(jié)果將是uint32_t
*(int16_t*)xxx 結(jié)果就是int16_t
根本得不到他所想要的與平臺(tái)相關(guān)的數(shù)據(jù)類型。
因此在C語言中,想要得到指針?biāo)鶎?duì)應(yīng)的整型類型,只能通過手動(dòng)指定,例如微軟就是這么干的
#ifdef _WIN64
  typedef __int64         intptr_t;
#else
  typedef int             intptr_t;
#endif
這樣intptr_t就可以確保能保存指針類型。
但是可惜這只是某些廠家這么干,ST的庫中并沒有這樣的類型,否則這事就好辦了(當(dāng)然了,ST目前可能也沒考慮推出64位的單片機(jī))
我不知道*(uint32_t*)&GPIOx這樣的代碼是否是出自ST的標(biāo)準(zhǔn)庫,我沒有去考證,如果真這樣寫的話他們自己可能也看著別扭,所以我看到st的某個(gè)版本庫里面看到的是直接比較指針,當(dāng)然了,就不能使用switch語句了,而是if語句,像這樣:
if (GPIOx == GPIOA) xxx_statement 。反正我覺得這樣寫是最直觀最易讀的了,給他們點(diǎn)個(gè)贊!
c語言的爭(zhēng)議太多了,就像#define與typedef之爭(zhēng),#define與const之爭(zhēng),程序員的理論就是運(yùn)行結(jié)果沒錯(cuò)那就都不是大問題。


完整的Word格式文檔51黑下載地址:
STM32中理解.zip (57.89 KB, 下載次數(shù): 11)


評(píng)分

參與人數(shù) 1黑幣 +100 收起 理由
admin + 100 共享資料的黑幣獎(jiǎng)勵(lì)!

查看全部評(píng)分

回復(fù)

使用道具 舉報(bào)

ID:258566 發(fā)表于 2018-10-23 11:40 | 顯示全部樓層
繪圖.png
回復(fù)

使用道具 舉報(bào)

本版積分規(guī)則

小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表