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