或許我們平時(shí)大多數(shù)學(xué)習(xí)C語(yǔ)言都是在Windows環(huán)境下學(xué)習(xí)的,對(duì)于程序執(zhí)行的底層邏輯了解的不是非常清楚,所以本文在這里給大家介紹一下,C語(yǔ)言在單片機(jī)中是如何執(zhí)行的。
Part1CPU與外設(shè)
我們知道,單片機(jī)也是有CPU的,它負(fù)責(zé)執(zhí)行代碼,運(yùn)算數(shù)據(jù),以及發(fā)出控制信號(hào)等功能,而與CPU直接相連的設(shè)備我們稱之為外設(shè)(就是集成芯片)。
本文以STM32F103ZET6
為例來(lái)講解,該芯片使用的是ARM架構(gòu),該架構(gòu)采用的是哈弗結(jié)構(gòu)。
- 哈弗結(jié)構(gòu):內(nèi)存和外設(shè)統(tǒng)一編址。
ARM芯片屬于精簡(jiǎn)指令集計(jì)算機(jī)(RISC:Reduced Instruction Set Computing),它所用的指令比較簡(jiǎn)單,有如下特點(diǎn):
- 對(duì)內(nèi)存只有讀、寫(xiě)指令;
- 對(duì)于數(shù)據(jù)的運(yùn)算是在CPU內(nèi)部實(shí)現(xiàn);
- 使用RISC指令的CPU復(fù)雜度小一點(diǎn),易于設(shè)計(jì)。
比如對(duì)于a=a+b這樣的算式,需要經(jīng)過(guò)下面4個(gè)步驟才可以實(shí)現(xiàn):
細(xì)看這幾個(gè)步驟,有些疑問(wèn),a的值讀出來(lái)后保存在CPU里面哪里?b的值讀出來(lái)后保存在CPU里面哪里?a+b的結(jié)果又保存在哪里?
如上圖所示,CPU也是由多個(gè)部分組成的,包括
ALU
邏輯運(yùn)算單元,控制單元,以及多個(gè)寄存器等等。
假設(shè)變量a的地址是0x12
,變量b的地址是0x34
,第一步的匯編代碼LDR R0, [a]
的意思就是將0x12
地址中的值讀取到R0寄存器中,第二步讀取b變量同理。
- LDR + 第一操作數(shù) + 第二操作數(shù):就是將第二操作數(shù)的值賦第一操作數(shù)。
當(dāng)變量a和變量b都被讀到了CPU的寄存器中后,執(zhí)行第三步匯編代碼ADDR R0, R0, R1
,意思是將R0和R1中的值相加,然后將結(jié)果保存到R0中。
- ADD:相加的匯編指令,可以有三個(gè)操作數(shù)也可以有兩個(gè)操作數(shù),三個(gè)操作數(shù)則后兩個(gè)操作數(shù)相加,得的結(jié)構(gòu)均保存到第一個(gè)操作數(shù)。
最后就是將R0中的計(jì)算結(jié)果再寫(xiě)回到內(nèi)存中,執(zhí)行第四步匯編代碼STR R0,[a]
,意思是將R0中的值寫(xiě)入到變量a的地址處0x12
。
如上圖所示,由于有32根地址線,所以CPU可訪問(wèn)的地址范圍就是
0x0000 0000 ~ 0xFFFF FFFF
,就拿我們熟知的Flash
和SRAM
來(lái)說(shuō),它倆和CPU直接相連,所以也可以看成是外設(shè)。
- Flash:用來(lái)存放用戶燒錄的程序,掉電數(shù)據(jù)不丟失(硬件特性)。
- SRAM:用來(lái)存放程序執(zhí)行過(guò)程中的臨時(shí)數(shù)據(jù),掉電數(shù)據(jù)丟失。
Flash
的地址范圍是0x0800 0000 ~ 0x0807 FFFF
,SRAM
的地址范圍是0x2000 0000 ~ 0x2000 FFFF
,這是我們根據(jù)上面的圖才知道的。
但是對(duì)于CPU而言,它并不知道哪里是FLASH
哪里是SRAM
,它只是被動(dòng)地在執(zhí)行代碼。CPU在一上電以后就從0x0000 0000
處開(kāi)始執(zhí)行代碼(可以進(jìn)行設(shè)置,以后再講解),直到調(diào)用了我們C代碼中必須有的main
函數(shù),然后進(jìn)入我們自己的邏輯當(dāng)中。
1.1 Flash
如上圖啟動(dòng)文件所示,CPU會(huì)通過(guò)
BL
匯編語(yǔ)句來(lái)調(diào)用main
函數(shù),但是在這之前,還會(huì)執(zhí)行LDR
匯編語(yǔ)句來(lái)給棧頂指針SP
賦值。
- BL:跳轉(zhuǎn)指令,也就是讓程序跳轉(zhuǎn)到指定位置處執(zhí)行,相當(dāng)于函數(shù)調(diào)用。
我們知道,代碼最終會(huì)被轉(zhuǎn)換成機(jī)器碼讓CPU去執(zhí)行,而存放這些機(jī)器碼也需要空間,所以代碼也是有地址的。
如上圖所示,無(wú)論是調(diào)用
main
函數(shù)之前的匯編代碼,還是main
函數(shù)的代碼,它們的地址都是0x0800 0xxx
,距離FLASH
的起始地址0x0800 0000
不是很遠(yuǎn),說(shuō)明我們燒錄到單片機(jī)中的代碼就是存放在FLASH
中的。
- 無(wú)論是
main
中的代碼,還是前面的匯編代碼,只要是從FLASH
起始處開(kāi)始的,都屬于我們程序員寫(xiě)的代碼。- 芯片廠家在
FLASH
起始地址之前,固化了一些代碼,這個(gè)暫不作說(shuō)明。
1.2 SRAM(內(nèi)存)
1.2.1 棧
當(dāng)main
執(zhí)行起來(lái)以后,運(yùn)算數(shù)據(jù)得到的臨時(shí)結(jié)果或者中間數(shù)據(jù)就都會(huì)暫存到SRAM
上,也就是我們平常所說(shuō)的內(nèi)存中。
如上圖所示,在使用BL
調(diào)用main函數(shù)之前,還使用了LDR
給棧頂指針SP
賦了初值,紅色箭頭指向的位置就是棧頂指針指向的位置。
代碼中的局部變量,函數(shù)棧幀等等數(shù)據(jù),全部都存放在SP開(kāi)始往下的位置,因?yàn)?棧的開(kāi)辟是從高地址向低地址 。
如上圖所示,在main
函數(shù)中創(chuàng)建兩個(gè)變量a和b,加volatile
的作用是防止編譯器將這兩個(gè)變量?jī)?yōu)化掉導(dǎo)致在這里無(wú)法演示現(xiàn)象。
main
函數(shù)也是被調(diào)用的,所以在其內(nèi)部創(chuàng)建的變量也屬于局部變量,局部變量就統(tǒng)統(tǒng)存放在棧上。
匯編代碼中,在創(chuàng)建變量a之前先執(zhí)行了一句PUSH {r2-r3,lr}
匯編語(yǔ)句,意思是將寄存器lr
,寄存器r2
和r3
中的值壓入棧中。
lr
:寄存器存放的是函數(shù)的返回地址,其實(shí)就是CPU中的r15
寄存器。PUSH
:執(zhí)行壓棧操作,將數(shù)據(jù)壓入到棧中后,棧頂指針向下移動(dòng)。
此時(shí)向棧中壓入了三個(gè)個(gè)數(shù)據(jù),每個(gè)數(shù)據(jù)都是4字節(jié)的,所以SP向下移動(dòng)了12個(gè)字節(jié),這12個(gè)字節(jié)就可以看作當(dāng)前main
函數(shù)的棧幀大小。
如上圖,當(dāng)執(zhí)行到給變量a賦值1時(shí),執(zhí)行了匯編代碼MOVS r0,#0x01
,表示將數(shù)值1賦值給寄存器r0
。然后再執(zhí)行匯編代碼STR r0,[sp,#0x04]
,表示將寄存器r0
中的值,寫(xiě)入到sp + 0x04
地址處。
- MOVS:將后一個(gè)操作數(shù)賦值給前一個(gè)操作數(shù)。
給變量b賦值2的時(shí)候,原理同上。所以此時(shí)在內(nèi)存中就存在了1和2兩個(gè)值,分別存在于sp+4
和sp+0
的位置處,后面用到變量a和b的時(shí)候,也是通過(guò)棧頂指針sp
來(lái)找這兩個(gè)值。
在這個(gè)過(guò)程中我們發(fā)現(xiàn),寄存器r2
和r3
的的作用就是 占坑 ,現(xiàn)在棧中給變量a和b占兩個(gè)位置,等到STR
賦值的時(shí)候?qū)⑦@兩個(gè)位置覆蓋即可。
那如果我創(chuàng)建100字節(jié)大小的數(shù)組呢?難道用100個(gè)寄存器來(lái)占坑嗎?顯然不可能,CPU一共也沒(méi)那么多寄存器。
如上圖所示,創(chuàng)建100字節(jié)大小的數(shù)組,先開(kāi)辟100個(gè)字節(jié)大小的??臻g,執(zhí)行匯編語(yǔ)句SUB sp,sp,#0x64
,表示用當(dāng)前的sp
值減去0X64
(100的16進(jìn)制),將結(jié)果再賦值到sp中。
- SUB:用法和ADD相似,只是作用是后兩個(gè)操作數(shù)做減法,得到的結(jié)果賦值給第一個(gè)操作數(shù)。
此時(shí)在SRAM
(內(nèi)存)上就存在一個(gè)100字節(jié)大小的棧用來(lái)存放這個(gè)str
數(shù)組,此時(shí)它不使用占坑的方式了,而是直接改變SP
的值來(lái)改變棧區(qū)的大小。
1.2.2 數(shù)據(jù)段
如上圖所示,創(chuàng)建兩個(gè)全局變量a和b,還有一個(gè)靜態(tài)變量c,在調(diào)試窗口中可以看到,變量a的地址是
0x20000 0000
,變量b的地址是0x20000 0004
,變量c的地址是0x2000 0008
,這三個(gè)變量緊挨著。
- 在C語(yǔ)言學(xué)習(xí)中我們知道,全局變量和靜態(tài)變量是存放在數(shù)據(jù)段的。
- 先忽略為什么它們的初始值都是0這個(gè)問(wèn)題。
在本文最前面放了一張內(nèi)存地址映射圖,其中SRAM
的地址范圍是0x2000 0000 ~ 0x20000 FFFF
,也就是說(shuō)內(nèi)存的起始地址就是0x2000 0000
,而變量a,b,c從起始位置開(kāi)始存放,所以說(shuō)這個(gè)位置就是數(shù)據(jù)段起始位置。
如上圖所示,當(dāng)給變量a賦值時(shí),先執(zhí)行MOVS r0,#0x01
,將數(shù)值1賦值給寄存器r0
,然后執(zhí)行LDR r1,[pc,#20]
語(yǔ)句,表示從PC + 20
的地址處讀取數(shù)據(jù)放入到寄存器r1
中。
- PC:程序計(jì)數(shù)器,實(shí)際上就是CPU寄存器中的R15,它存放程序的地址,其值永遠(yuǎn)是當(dāng)前語(yǔ)句的下一條語(yǔ)句的地址。
- CPU會(huì)根據(jù)PC值去執(zhí)行對(duì)應(yīng)的指令。
PC + 20
的值是0x0800 0016C
,這是一個(gè)Flash
處的地址,而該地址處的值是0x0000
,由于LDR
一次取四個(gè)字節(jié)的數(shù)據(jù),所以要連0x0800 0016E
處的值0x2000
也要讀走,兩個(gè)值按照大端存儲(chǔ)模式復(fù)原(高地址存放高字節(jié)序),得到的值就是0x2000 0000
。
所以此時(shí)寄存器r1
中的值就是0x2000 0000
,再執(zhí)行STR r0,[r1,#0x00]
匯編語(yǔ)句,將r0中的1寫(xiě)入到0x20000 0000
處,也就是數(shù)據(jù)段變量a的地址處,此時(shí)就成功改變了它的值。
1.2.3 堆
如上圖,整個(gè)
SRAM
上,棧占用一部分空間,它的大小隨著的SP
的變化而變化,數(shù)據(jù)段占用一部分空間,但是還沒(méi)有全部使用完畢,還有剩余的空閑空間,堆就建立在這部分空間上。
- 堆空間的大小并不會(huì)發(fā)生變化,它就是一塊固定大小的空間,用戶可以去申請(qǐng)使用,用完了還必須歸還。
所以可以用一個(gè)大的全局?jǐn)?shù)組來(lái)管理這塊空間,因?yàn)槿謹(jǐn)?shù)組存放在數(shù)據(jù)段,它的大小并不會(huì)隨著SP
的變化而變化,從而堆空間的大小也不會(huì)變化。
- 雖然叫做堆,但是這部分空間仍然屬于數(shù)據(jù)段,只是提供了接操作這部分空間的接口。
如上圖所示,在此定義了一個(gè)全局?jǐn)?shù)組
char buffer[500]
來(lái)充當(dāng)堆,還有一個(gè)全局的index
用來(lái)記錄堆的使用情況,又實(shí)現(xiàn)了一個(gè)mymalloc
用來(lái)向堆區(qū)申請(qǐng)空間。
圖
全局?jǐn)?shù)組buffer
的地址是0x2000 0010
,排在a,b,c,index后面,第一次mymalloc
以后,得到的地址是0x2000 0010
,大小是100個(gè)字節(jié),第二次mymalloc
以后,得到的地址是0x2000 0074
,地址相差0x64
也就是100,說(shuō)明這是在第一次申請(qǐng)的基礎(chǔ)上再次申請(qǐng)的。index
的值是0x12C
也就是300,說(shuō)明一共申請(qǐng)了300個(gè)字節(jié)的空間。
自定義的釋放函數(shù)myfree
在此就不寫(xiě)了,各位小伙伴可以自行嘗試。所以說(shuō), 堆本質(zhì)上就是就是一塊空閑內(nèi)存,可以使用malloc/free函數(shù)來(lái)管理它 。
為什么Flash
的起始地址就是0x0800 0000
,SRAM
的起始地址就是0x2000 0000
?不能是別的嗎?
如上圖所示,在MDK中,連接器選項(xiàng)中R/O Base
是Flash
基地址,用來(lái)設(shè)置Flash
的起始地址,R/W Base
是SRAM
基地址,用來(lái)設(shè)置SRAM
的起始地址。
下面藍(lán)色框中的是連接器控制信息,里面的內(nèi)容是我們程序員寫(xiě)的,目的是告訴連接器要做什么。
默認(rèn)情況下,紅色框中的SRAM
起始地址是0x2000 0000
,本文將其改成了0x2000 8000
,來(lái)看一下會(huì)發(fā)生什么?
如上圖所示,此時(shí)代碼里只有一個(gè)全局變量a,它位于數(shù)據(jù)段的起始位置,也就是
SRAM
的起始位置,其地址是0x2000 8000
,本文成功地修改了SRAM
的起始地址。
Flash
的地址也是同理,也可以通過(guò)連接器R/O Base
進(jìn)行修改。
Part2變量的初始化
- 變量:能改變的量,它一定在內(nèi)存上占據(jù)空間,
2.1 局部變量
如上圖所示,在
main
函數(shù)中創(chuàng)建了局部變量a并賦值0x11223344
,創(chuàng)建了局部變量b并賦值0x11
。在匯編代碼中,首先移動(dòng)SP
,由于只有兩個(gè)變量,所以壓棧r2
和r3
來(lái)占位。
初始化變量a的時(shí)候,先執(zhí)行LDR r0,[pc,#12]
匯編語(yǔ)句,取地址為0x0800140
的Flash
中取值,讀取了該地址及下個(gè)地址供四個(gè)字節(jié)數(shù)據(jù)0x11223344
,賦值給寄存器r0
。然后再執(zhí)行STR r0,[sp,#0x04]
匯編語(yǔ)句,將r0
中的0x11223344
賦值給變量a所在處。
初始化變量b的時(shí)候,先執(zhí)行MOVS r0,#0x11
匯編語(yǔ)句,直接將立即數(shù)#0x11
賦值給寄存器r0
,然后再執(zhí)行STR r0,[sp,#0x00]
匯編語(yǔ)句,將r0
中的0x11
賦值給變量b所在處。
- 兩個(gè)局部變量的初始化過(guò)程并不一樣,初始值為4字節(jié)的變量需要去
Flash
中取初值,初始值為1字節(jié)的變量,直接就給賦值了。
指令也是有大小的,如0x08000132 4803 LDR r0,[pc,#12]
中,0x08000132
是代碼所在的Flash
地址,4803
是代碼匯編之后的機(jī)器碼,大小是2字節(jié)(CPU執(zhí)行的是機(jī)器碼,匯編語(yǔ)句是為了方便我們看的,剩下的就是匯編語(yǔ)句)。
對(duì)于初始值為0x#11
的初始化,兩個(gè)字節(jié)的指令足夠容納一個(gè)字節(jié)的初值,所以直接就賦值初始化了。
對(duì)于初始值為0x11223344
的初始化,兩個(gè)字節(jié)的指令無(wú)法容納四個(gè)字節(jié)的初值,所以必須取Flash
中取初值到寄存器中,然后再進(jìn)行賦值。
如上圖,創(chuàng)建一個(gè)
char buffer[500]
數(shù)組全部用1初始化,使用BL.W
指令跳轉(zhuǎn)到__aeabi_memclr4
處進(jìn)行初始化,相當(dāng)于調(diào)用了一個(gè)函數(shù)來(lái)初始化這個(gè)數(shù)組,這個(gè)函數(shù)是由編譯器生成的,也是一堆匯編語(yǔ)句,這里暫不做介紹。
如上圖,當(dāng)
main
函數(shù)執(zhí)行完,執(zhí)行了return 0
以后,會(huì)執(zhí)行POP {r2-r3,pc}
匯編語(yǔ)句,將前面壓棧時(shí)向下生長(zhǎng)的空間回收,也就是SP
向上移動(dòng)。
- POP:出棧操作,將棧中的數(shù)據(jù)彈出,并且
SP
棧頂指針向上移動(dòng)。
此時(shí)原本存放變量a和b的空間就位于棧外面了,原本的值彈出給了r0
和r1
,PC
拿到函數(shù)的返回地址lr
。
雖然a和b的內(nèi)存空間還存在,但是已經(jīng)不再被維護(hù)了,當(dāng)有新的局部變量需要棧的時(shí)候,SP
會(huì)重新向下移動(dòng),并且使用新的值覆蓋掉這部分空間。
2.2 全局變量和靜態(tài)變量
如上圖所示,定義兩個(gè)全局變量a和b,初始值分別為10和20,定義一個(gè)全局靜態(tài)變量,初始值為30,定義一個(gè)局部靜態(tài)變量,初始值為40,當(dāng)程序執(zhí)行到main
中時(shí),通過(guò)調(diào)試窗口看到它們的值都是0,并沒(méi)有被初始化。
如上圖,在啟動(dòng)文件中使用
BL
跳轉(zhuǎn)到main
函數(shù)之前,需要先跳轉(zhuǎn)到copy
函數(shù),將全局變量的初始值全部復(fù)制到對(duì)應(yīng)數(shù)據(jù)段的地址。但是這里并沒(méi)有實(shí)現(xiàn)copy
函數(shù),所以全局變量沒(méi)有被初始化。
- 全局變量的初始值是存放在
Flash
中的,注意是只存放初始值,不存放變量名,因?yàn)镃PU執(zhí)行的是機(jī)器碼,機(jī)器碼中并沒(méi)有變量名這么一說(shuō)。
如上圖,仍然是這四個(gè)變量,但是在定義都是時(shí)候都沒(méi)有給初始值,沒(méi)有進(jìn)行初始化,但是在調(diào)試窗口看到它們的值仍然是0。
- 對(duì)于沒(méi)有初始值的數(shù)據(jù)段變量,在編譯的時(shí)候,編譯器會(huì)用0將這些變量初始化,也就是將對(duì)應(yīng)地址寫(xiě)0。
相當(dāng)于會(huì)調(diào)用一個(gè)memset
函數(shù)將這部分變量全部初始化為0。這些變量處于數(shù)據(jù)段的 未初始化數(shù)據(jù)段 ,而前面有初始值的處于 已初始化數(shù)據(jù)段 。
如上圖所示,便是整個(gè)數(shù)據(jù)段的內(nèi)存示意圖。
在STM32F103中,代碼是在FLASH
中運(yùn)行的,并不會(huì)加載到內(nèi)存中,而且代碼和數(shù)據(jù)段的初始值是混合存放在Flash
中的。
Part3函數(shù)
如上圖所示,
Add
函數(shù)其實(shí)就是8條匯編指令,調(diào)用函數(shù)就是讓CPU的PC
寄存器等于8條指令的首地址,也就是函數(shù)地址。如上圖,
main
函數(shù)開(kāi)辟一次棧,SP位于上圖紅色位置,棧里有變量a和b以及main函數(shù)的返回地址lr
。
在調(diào)用Add
函數(shù)的時(shí)候,會(huì)再壓一次棧,SP位于上圖綠色位置,這次壓入了Add函數(shù)的返回地址lr
,以及形參v,再執(zhí)行SUB
語(yǔ)句為局部變量a開(kāi)辟空間,SP位于上圖藍(lán)色位置。
- 函數(shù)傳參通過(guò)寄存器
r0
實(shí)現(xiàn),在PUSH
的時(shí)候,r0
中已經(jīng)有了實(shí)參,然后將實(shí)參壓入調(diào)用函數(shù)的棧中成為形參。
然后執(zhí)行LDR
和STR
將形參的值拿到局部變量a中,再進(jìn)行加一操作,操作完畢后將結(jié)果再度寫(xiě)入到形參v的位置,當(dāng)函數(shù)返回時(shí),執(zhí)行LDR
將運(yùn)算結(jié)果存入r0
寄存器中,然后POP
出棧操作,SP重新位于上圖紅色位置。
- 函數(shù)返回值的時(shí)候,同樣通過(guò)
r0
實(shí)現(xiàn),SP雖然向上移動(dòng)了,但是r0
中有返回值。
調(diào)用函數(shù)結(jié)束后,執(zhí)行STR
將r0
中的運(yùn)算結(jié)果寫(xiě)入到變量b。
如上圖,
main
函數(shù)在調(diào)用Add_Sum
函數(shù)的時(shí)候,一次傳入了八個(gè)變量,賦了初值以后,將其中的四個(gè)變量交給了寄存器r3-r7
,然后執(zhí)行STM sp,[r8-r11]
,將剩下的四個(gè)變量繼續(xù)壓棧。
- STM:一次存儲(chǔ)多個(gè)寄存器中的值到指定位置。
在執(zhí)行Add_Sum
函數(shù)的時(shí)候,執(zhí)行LDM r5,[r5-r7,r12]
,從棧中將后四個(gè)變量取出來(lái),再與寄存器r3-r7
中的四個(gè)值一起求和,最后將結(jié)果返回。
- LDM:一次讀取多個(gè)值到多個(gè)寄存器中。
調(diào)用函數(shù)時(shí),如果傳入的變量比較多,或者是數(shù)組的話,由于沒(méi)有那么多的寄存器可以做中間人,所以會(huì)將這些變量繼續(xù)壓入調(diào)用方的棧中,被調(diào)用函數(shù)在用的時(shí)候從調(diào)用方的棧中拿走進(jìn)行拷貝。
這就是為什么我們?cè)诤瘮?shù)中改變形參,并不影響實(shí)參的原因,因?yàn)樵诤瘮?shù)中形參是實(shí)參的拷貝,它位于函數(shù)的棧中,調(diào)用方的棧并不受影響。
Part4指針變量
如上圖,創(chuàng)建了一個(gè)int類型的變量,一個(gè)char類型的變量,一個(gè)int* 類型的變量,一個(gè)char* 類型的變量,從匯編處可以看出,指針變量同樣要在棧中占用空間,只是初始化的時(shí)候,指針變量賦值的是地址,如
ADD r2,sp,#0x04
,就是將棧頂指針向上移動(dòng)4個(gè)字節(jié)后的地址賦值給為int* pa
變量占坑的r2
。
- 指針變量仍然是變量,是變量就要占據(jù)內(nèi)存空間,和普通的變量沒(méi)有區(qū)別,只是它的值是地址而已。
在訪問(wèn)這兩個(gè)指針變量時(shí),*pa = 20
,執(zhí)行了STR r0,[r2,0x00]
,一次給變量a寫(xiě)入四個(gè)字節(jié),*pb = 'B'
,執(zhí)行了STRB r0,[r11#0x00]
,一次給變量b寫(xiě)入一個(gè)字節(jié)。
- STRB:存儲(chǔ)一個(gè)字節(jié)數(shù)據(jù),作用和STR一樣,只是寫(xiě)入字節(jié)是一個(gè)字節(jié)。
訪問(wèn)不同類型的指針,底層會(huì)有不同的策略,讓CPU以對(duì)應(yīng)的視角去操作對(duì)應(yīng)的內(nèi)存。如*pa
,CPU就會(huì)認(rèn)為它現(xiàn)在訪問(wèn)地址處的變量是一個(gè)int
類型,而不是一個(gè)char
類型。
如上圖,創(chuàng)建函數(shù)指針變量
int(*pf)(volatile int)
,將函數(shù)Add
地址賦值給變量pf。執(zhí)行LDR r4,[pc,#12]
到Flash
的0x0800 0158
處取函數(shù)地址為0x0800 0131
。
但是我們看到函數(shù)的8條指令的起始地址是0x0800 0130
,與r4
中取到的函數(shù)地址相差1,這是因?yàn)樵?code>0x0800 0158處存放的0x0800 0131
代表兩層意思。
- 函數(shù)地址的最低位為1表示該函數(shù)使用的是
Thumb
指令集,這個(gè)1和實(shí)際地址沒(méi)有關(guān)系。- 該值減去1才是真正的函數(shù)起始地址,也就是
0x0800 0130
。
無(wú)論什么類型的指針變量,它里面存放的都是相應(yīng)變量的首地址,包括函數(shù)指針變量,再通過(guò)策略決定CPU讀寫(xiě)該首地址后面幾個(gè)字節(jié)。
Part5結(jié)構(gòu)體和聯(lián)合體
如上圖,創(chuàng)建一個(gè)局部結(jié)構(gòu)體變量,有三個(gè)成員變量int age,char sex,int score,并且給它們初始化。先執(zhí)行
LDR
拿到在Flash
中存放初始值的地址0x0800 0144
到r2
中,然后再執(zhí)行LDM
從初值起始地址開(kāi)始讀取初值0x0000 18
,0x0000 00001
,0x0000 0064
,對(duì)應(yīng)著24,1,100。
- 結(jié)構(gòu)體初始化時(shí),初值存放在
Flash
中,需要讀取到寄存器中,然后再賦值給結(jié)構(gòu)體各個(gè)成員。
通過(guò)調(diào)試窗口查看三個(gè)成員的地址,發(fā)現(xiàn)成員之間的地址相差4個(gè)字節(jié),其中int age
和int score
是四字節(jié)變量占用4個(gè)空間,但是char sex
是一字節(jié)變量也占用四個(gè)空間。
如上圖中SRAM
示意圖所示,此時(shí)sex
的四個(gè)字節(jié)中只用了一個(gè)字節(jié),浪費(fèi)了三個(gè)字節(jié)。
- 為了提高結(jié)構(gòu)體的訪問(wèn)效率,結(jié)構(gòu)體變量在存放時(shí)會(huì)進(jìn)行內(nèi)存對(duì)齊。
如上圖,數(shù)據(jù)線和地址線都是32位的,也就是4字節(jié),除此之外還有四根控制線be0,be1,be2,be3
。無(wú)論是訪問(wèn)還是寫(xiě)入,CPU一次操作都是四個(gè)字節(jié)的內(nèi)存。
當(dāng)be0
有效時(shí),CPU操作4個(gè)字節(jié)中第1個(gè)字節(jié)的空間,be1
有效就操作第2個(gè)字節(jié)的空間,be2
有效就操作第3個(gè)字節(jié)的空間,be3
有效就操作第4個(gè)字節(jié)的空間。
如果操作的是第一個(gè)4字節(jié)中的3個(gè)字節(jié)和第二個(gè)4字節(jié)的1個(gè)字節(jié)組成的四字節(jié)空間,CPU就需要操作兩次,第一次操作時(shí)be1,be2,be3
有效,第二次操作時(shí)be0
有效,最后組合得到需要的數(shù)據(jù)。
采用結(jié)構(gòu)體內(nèi)存對(duì)齊方案,雖然char sex
浪費(fèi)了三個(gè)字節(jié)的空間,但是在操作int score
的時(shí)候,可以一次性操作完畢,不需要第二次。
- 結(jié)構(gòu)體對(duì)齊利用了以空間換時(shí)間的思想。
如上圖,創(chuàng)建一個(gè)位段結(jié)構(gòu)體,成員
age
和sex
都只占用int
的32個(gè)比特位中的1個(gè)比特位,成員score
占4個(gè)字節(jié)32個(gè)比特位。
先執(zhí)行LDR
取數(shù)據(jù),然后執(zhí)行BIC r0,r0,#0x01
將r0中的32個(gè)比特位的第一個(gè)比特位清0,然后再執(zhí)行ADDS r0,r0,#1
讓第一個(gè)比特位的值成為1,此時(shí)給int age:1
初始化完成。
- BIC:清除指定比特位,讓該位為0。
同理,再給int sex:1
初始化為1,也就是讓32個(gè)比特位中的第二個(gè)比特位為1。此時(shí)還剩下30個(gè)比特位被浪費(fèi)掉了,下一個(gè)int score
占用完整的32個(gè)比特位,同樣是為了提高效率。
如上圖,結(jié)構(gòu)體中又增加了一個(gè)聯(lián)合體成員
union weight
,char kg
和int g
兩種類型的變量共用這一個(gè)空間。而且可以看到,weight
,kg
,g
三者的地址都是0x2000 FFF8
。
在給成員kg
賦值80的時(shí)候,整個(gè)weight
空間的值是0x0000 0050
,在給成員g
賦值的時(shí)候,整個(gè)weight
空間的值是0x0001 3880
。操作char
類型成員,只改變4個(gè)字節(jié)中的一個(gè)字節(jié),操作int
類型成員,則4個(gè)字節(jié)全部改變。
對(duì)應(yīng)的匯編代碼中,操作char
成員使用的是STRB
,操作int
成員使用的是STR
。
Part6總結(jié)
如上圖便是在這篇文章中講解的ARM架構(gòu)部分模型,以及常用C語(yǔ)言知識(shí)在ARM架構(gòu)中是如何體現(xiàn)的。
程序在經(jīng)過(guò)預(yù)處理,編譯,匯編,最后再經(jīng)過(guò)連接器分配地址形成.axf
,.bin
,或者.hex
等類型的文件,這幾種文件中的內(nèi)容全部都是機(jī)器碼。
將最終的機(jī)器碼燒錄到單片機(jī)中,單片機(jī)一上電就開(kāi)始執(zhí)行這些機(jī)器碼,執(zhí)行過(guò)程中是沒(méi)有編譯器,電腦系統(tǒng)的參與的,無(wú)論是變量的定義,初始化,還是內(nèi)存空間的分配,你還能說(shuō)是自動(dòng)完成的嗎?
所以說(shuō),當(dāng)程序在單片機(jī)中開(kāi)始運(yùn)行的時(shí)候,它的一切就早被安排好了,就是按照前面所講述的去安排設(shè)計(jì)的,CPU只需要按照機(jī)器碼執(zhí)行即可。
-
單片機(jī)
+關(guān)注
關(guān)注
6067文章
44992瀏覽量
650676 -
WINDOWS
+關(guān)注
關(guān)注
4文章
3614瀏覽量
91438 -
C語(yǔ)言
+關(guān)注
關(guān)注
180文章
7632瀏覽量
141830 -
程序
+關(guān)注
關(guān)注
117文章
3826瀏覽量
83013
發(fā)布評(píng)論請(qǐng)先 登錄
STM32單片機(jī)中的C語(yǔ)言基礎(chǔ)知識(shí)
C語(yǔ)言在單片機(jī)開(kāi)發(fā)中的應(yīng)用
單片機(jī)C語(yǔ)言編程中“位”的保存方案
AVR單片機(jī)C語(yǔ)言總綱
單片機(jī)C語(yǔ)言輕松入門
單片機(jī)C語(yǔ)言編程與實(shí)例
單片機(jī)c語(yǔ)言教程
8051單片機(jī)C語(yǔ)言軟件設(shè)計(jì)8051單片機(jī)C語(yǔ)言軟件設(shè)計(jì)

學(xué)習(xí)單片機(jī)一定要先學(xué)好C語(yǔ)言再去學(xué)單片機(jī)嗎
單片機(jī)C語(yǔ)言程序設(shè)計(jì)的詳細(xì)資料

單片機(jī)C語(yǔ)言和C語(yǔ)言為什么有差異?
1.單片機(jī)和C語(yǔ)言的關(guān)系(5)

C語(yǔ)言條件編譯語(yǔ)句and單片機(jī)DMA的介紹

評(píng)論