1、背景
計算機語言具有高級語言和低級語言之分。而高級語言又主要是相對于匯編語言而言的,它是較接近自然語言和數(shù)學(xué)公式的編程,基本脫離了機器的硬件系統(tǒng),用人們更易理解的方式編寫程序。編寫的程序稱之為源程序。低級語言分機器語言(二進制語言)和匯編語言(符號語言),這兩種語言都是面向機器的語言,和具體機器的指令系統(tǒng)密切相關(guān)。機器語言用指令代碼編寫程序,而符號語言用指令助記符來編寫程序。高級語言、匯編語言和機器語言都是用于編寫計算機程序的語言。
1.1定義
機器語言將指令編碼為 0 和 1 的序列;這種二進制編碼是計算機的處理器被構(gòu)建來執(zhí)行的。但是,使用這種編碼編寫程序?qū)τ谌祟惓绦騿T來說是笨拙的。因此,當(dāng)程序員想要指示計算機要執(zhí)行的精確指令時,他們會使用匯編語言,該語言允許以文本形式編寫指令。匯編器將包含匯編語言代碼的文件翻譯成相應(yīng)的機器語言。
讓我們看一個 ARM 設(shè)計的簡單示例。這是機器語言指令:
1110 0001 1010 0000 0011 0000 0000 1001
當(dāng)處理器被告知執(zhí)行該二進制序列時,它會將值從“register 9”復(fù)制到“register 9”。但作為程序員,您幾乎不想閱讀長二進制序列并理解它。相反,程序員更喜歡用匯編語言進行編程,我們將使用以下行來表達這一點。
MOV R3, R9
然后程序員將使用匯編程序?qū)⑵滢D(zhuǎn)換為計算機實際執(zhí)行的二進制編碼。
但不只是只有一種機器語言:為每條處理器設(shè)計了不同的機器語言,旨在提供一組強大的快速指令,同時允許構(gòu)建相對簡單的電路。處理器通常被設(shè)計為與以前的處理器兼容,因此它遵循相同的機器語言設(shè)計。例如,英特爾的處理器系列(包括 80386、奔騰和酷睿 i7)支持類似的機器語言。但是 ARM 處理器支持完全不同的機器語言。機器語言編碼的設(shè)計稱為指令集架構(gòu)(ISA,instruction set architecture)。
并且對于每種機器語言,必須有不同的匯編語言,因為匯編語言必須對應(yīng)于一組完全不同的機器語言指令。
1.2不同的指令集架構(gòu)(ISA)
在眾多 ISA(指令集架構(gòu))中,x86 是最廣為人知的。它最初由 Intel 于 1974 年設(shè)計用于 8 位處理器(Intel 8080),多年來它擴展到 16 位形式(1978,Intel 8086),然后擴展到 32 位形式(1985,Intel 80386),然后是 64 位形式(2003,AMD Opteron)。今天,支持 IA32 的處理器現(xiàn)在由 Intel、AMD 和 VIA 制造,并且可以在大多數(shù)個人計算機中找到。
今天另一個著名的 ISA 是 PowerPC。Apple 的 Macintosh 計算機一直使用這些處理器,直到 2006 年 Apple 將其計算機切換到 x86 系列處理器。但 PowerPC 仍然普遍用于汽車和游戲機(包括 Wii、Playstation 3 和 XBox 360)等應(yīng)用程序。
但我們將研究的 ISA 來自一家名為 ARM 的公司。(與其他成功的 ISA 一樣,ARM 的 ISA 多年來一直在發(fā)展。我們將研究 4T 版本。)支持 ARM ISA 的處理器分布相當(dāng)廣泛,通常用于手機、數(shù)字音樂播放器和手持游戲系統(tǒng)等低功耗設(shè)備。iPhone、Kindle 和 Nintendo DS 都是采用 ARM 處理器的設(shè)備的突出例子。
我們研究 ARM 的 ISA 而不是 IA32 有幾個原因。
匯編語言編程很少用于功能更強大的計算系統(tǒng),因為用高級編程語言進行編程要容易得多。但是對于小型設(shè)備,匯編語言編程仍然很重要:由于功率和價格的限制,設(shè)備的資源非常少,開發(fā)人員可以使用匯編語言盡可能高效地使用這些資源。
IA32 架構(gòu)的多個擴展導(dǎo)致它過于復(fù)雜,以至于我們無法真正徹底理解。
IA32 可以追溯到 1970 年代,那是一個完全不同的計算時代。ARM 更能代表更現(xiàn)代的 ISA 設(shè)計。
2ARM匯編語言基礎(chǔ)
2.1一個簡單的程序:數(shù)字求和
讓我們從一個簡單的例子開始我們的介紹。想象一下,我們想要將 1 到 10 的數(shù)字相加。我們可以在 C 語言中這樣做,如下所示。
下面將其翻譯成 ARM 的 ISA 支持的指令。
您會注意到在匯編語言程序中提到了 R0 和 R1。這些是對寄存器的引用,它們位于處理器中,用于在計算期間存儲數(shù)據(jù)。ARM 處理器包括 16 個易于訪問的寄存器,編號為 R0 到 R15。每個都存儲一個 32 位數(shù)字。請注意,盡管寄存器存儲數(shù)據(jù),但它們與內(nèi)存的概念非常不同:內(nèi)存通常更大(千字節(jié)或通常千兆字節(jié)),因此它通常存在于處理器之外。由于內(nèi)存的大小,訪問內(nèi)存比訪問寄存器需要更多的時間——通常大約是 10 倍。因此,匯編語言編程傾向于盡可能關(guān)注使用寄存器。
因為匯編語言程序的每一行都直接對應(yīng)于機器語言,所以這些行的格式受到嚴格限制??梢钥吹矫恳恍杏蓛刹糠纸M成:首先是操作碼,例如 MOV,它是表示操作類型的縮寫;之后是諸如“R0,#0”之類的參數(shù)。每個操作碼對允許的參數(shù)都有嚴格的要求。例如,一條 MOV 指令必須正好有兩個參數(shù):第一個必須標(biāo)識一個寄存器,第二個必須提供一個寄存器或一個常量(以“#”為前綴)。直接放在指令中的常量稱為立即數(shù),因為處理器在讀取指令時可以立即使用它。
在上述匯編語言程序中,我們首先使用 MOV 指令將 R0 初始化為 0,將 R1 初始化為 10。ADD 指令計算 R0 和 R1 的和(第二個和第三個參數(shù))并將結(jié)果放入 R0(第一個參數(shù)) );這對應(yīng)于總數(shù) += i;等效 C 程序的行。隨后的 SUBS 指令將 R1 減 1。
要理解下一條指令,我們需要了解除了寄存器 R0 到 R15 之外,ARM 處理器還包含一組四個“標(biāo)志”,分別標(biāo)記為零標(biāo)志 (Z)、負標(biāo)志 (N)、進位標(biāo)志 (C) 和溢出標(biāo)志 (V)。每當(dāng)算術(shù)指令的末尾有一個 S 時,就像 SUBS 一樣,這些標(biāo)志將根據(jù)計算結(jié)果進行更新。在這種情況下,如果 R1 減 1 的結(jié)果為 0,則 Z 標(biāo)志將變?yōu)?1;N、C 和 V 標(biāo)志也更新了,但它們與我們對此代碼的討論無關(guān)。
下面的指令 BNE 將檢查 Z 標(biāo)志。如果未設(shè)置 Z 標(biāo)志(即,先前的減法給出非零結(jié)果),則 BNE 安排處理器,以便執(zhí)行的下一條指令是 ADD 指令,再次標(biāo)記;這導(dǎo)致以較小的 R1 值重復(fù)循環(huán)。如果設(shè)置了 Z 標(biāo)志,處理器將繼續(xù)執(zhí)行下一條指令。(BNE 代表 Branch if Not Equal。這個名字來源于想象我們想要檢查兩個數(shù)字是否相等。使用 ARM 的 ISA 執(zhí)行此操作的一種方法是首先告訴處理器減去兩個數(shù)字;如果差是零,那么這兩個數(shù)字必須相等,零標(biāo)志將為 1。它們導(dǎo)致零,這將設(shè)置零標(biāo)志。)
最后一條指令 B 總是分支回到指定的指令。在這個程序中,指令為自己命名,通過使計算機進入一個緊密的無限循環(huán)來有效地停止程序。
2.2另一個例子:冰雹序列
輸入任何一個大于1的正整數(shù)N,如果是偶數(shù)的話就除以2,如果是奇數(shù)的話就乘以3再加上1,最后這個數(shù)都會變?yōu)?。特殊地,當(dāng)輸入為1時,序列為1。這就是冰雹序列。其公式如下:
現(xiàn)在,讓我們考慮一下冰雹序列。給定一個整數(shù) n,我們反復(fù)想應(yīng)用以下過程:
迭代次數(shù)←0
當(dāng) n ≠ 1 時:迭代器←迭代器+1
如果 n 是奇數(shù):n ← 3 ⋅ n + 1
別的:n ← n / 2
例如,如果我們從 3 開始,那么因為這是奇數(shù),所以我們的下一個數(shù)字是 3 ⋅ 3 + 1 = 10。這是偶數(shù),所以我們的下一個數(shù)字是 10 / 2 = 5。這是奇數(shù),所以我們的下一個數(shù)字是3 ⋅ 5 + 1 = 16。這是偶數(shù),所以我們轉(zhuǎn)到 8,它仍然是偶數(shù),所以我們轉(zhuǎn)到 4,然后是 2 和 1。
在將其翻譯成 ARM 的匯編語言時,我們必須面對一個事實,即 ARM 缺少任何與除法相關(guān)的指令。(設(shè)計人員認為除法很少需要在它所需的復(fù)雜電路上浪費晶體管。)幸運的是,該算法中的除法相對簡單:我們只需將 n 除以 2,這可以通過右移來完成。
ARM 有一種不同尋常的移位方法:我們已經(jīng)看到,每條基本算術(shù)指令,最后的參數(shù)都可以是常量(如 SUBS R1、R1、#1)或寄存器(如 ADD R0、R0、R1)。但是當(dāng)最后一個參數(shù)是一個寄存器時,我們可以選擇添加一個移位距離:例如,指令“ADD R0, R0, R1, LSL #1”。表示在將 R1 添加到 R0 之前添加左移版本的 R1(而 R1 本身保持不變)。ARM 指令集支持四種類型的移位:
LSL |
logical shift left(邏輯左移); |
LSR |
logical shift right(邏輯右移); |
ASR |
arithmetic shift right(算術(shù)右移); |
ROR |
rotate right(向右旋轉(zhuǎn)); |
移位距離可以是 1 到 32 之間的立即數(shù),也可以基于寄存器值:“MOV R0, R1, ASR R2”等價于“R0 = R1 >> R2”。
在將我們的偽代碼翻譯成匯編語言時,我們會發(fā)現(xiàn)移位操作對于將 n 乘以 3(計算為 n + (n « 1))和除以 n (計算為 n » 1)都很有用。我們還需要處理測試 n 是否為奇數(shù)。我們可以通過測試 n 的 1 位是否設(shè)置來做到這一點,我們可以使用 ANDS 指令與 1 進行按位與來完成。ANDS 指令根據(jù)結(jié)果是否為 0 設(shè)置 Z 標(biāo)志。如果結(jié)果為 0,那么這意味著n的1位是0,所以n是偶數(shù)。
2.3另一個例子:添加數(shù)字
讓我們看另一個例子。在這里,假設(shè)我們要添加一個正數(shù)的數(shù)字;例如,給定數(shù)字 1024,我們想要計算 1 + 0 + 2 + 4,即 7。用 C 語言表達這一點的明顯方法如下。
但是,很難將其轉(zhuǎn)換為 ARM 的 ISA,因為 ARM 沒有任何除法指令。但是,我們可以使用一個巧妙的技巧來使用乘法來執(zhí)行這種除法:如果我們將一個數(shù)字乘以 232 / 10,則乘積的高 32 位告訴我們將原始數(shù)字除以 10 的結(jié)果。這種解法導(dǎo)致以下替代方法對數(shù)字中的數(shù)字求和。
在將其翻譯成匯編代碼時,我們必須面對兩個問題。更明顯的是確定使用哪個指令來執(zhí)行乘法。在這里,我們要使用 UMULL 指令(Unsigned MULtiply Long),它將兩個寄存器解釋為無符號的 32 位數(shù)字,并將寄存器值的 64 位乘積放入兩個不同的寄存器中。下面的例子說明了。
我們必須面對的不太明顯的問題是將 0x1999999A 放入寄存器中。一開始您可能會想使用 MOV,但這條指令有一個主要限制:任何立即數(shù)都必須循環(huán)偶數(shù)位才能達到 8 位值。對于 0 到 255 之間的數(shù)字,這不是問題;對于 1,024 也不是問題,因為 0x400 可以通過將 1 向左旋轉(zhuǎn) 12 位來實現(xiàn)。但是對于 0x1999999A 沒有辦法做到這一點。我們將使用的解決方案是分別加載每個字節(jié),使用 ORR 指令將它們連接起來,該指令計算兩個值的按位或。
順便說一句,您有時可能希望將一個小的負數(shù)(如 -10)放入寄存器中。您不能使用 MOV 來完成此操作,因為它的二進制補碼表示為 0xFFFFFFF6,無法旋轉(zhuǎn)為 8 位數(shù)字。如果碰巧知道某個寄存器包含數(shù)字 0,那么您可以使用 SUB。但如果不是,則 MVN(MoVe Not)指令很有用:它將其參數(shù)的按位 NOT 放入目標(biāo)寄存器。因此,要將 -10 放入 R0,我們可以使用“MVN R0,#0x9”。
2.4計算的指令摘要
ARM 包括 16 條“基本”算術(shù)指令,編號從 0 到 15。下面列出了所有 16 條指令,其功能由相關(guān)的 C 運算符總結(jié)。(每行開頭的數(shù)字用于將指令翻譯成機器語言。程序員沒有理由記住這種對應(yīng)關(guān)系:畢竟,這就是我們有匯編程序的原因。)
除 TST、TEQ、CMP 和 CMN 外,所有指令都可以在操作碼后綴 S 以表示操作應(yīng)設(shè)置標(biāo)志。對于 TST、TEQ、CMP 和 CMN,S 是隱含的:指令不會更改任何通用寄存器,因此執(zhí)行指令的唯一要點是設(shè)置標(biāo)志。
我們還看到了上述基本算術(shù)指令中沒有的其他三個操作碼:UMULL 是“非基本”算術(shù)指令,B 和 BNE 不是算術(shù)指令。
2.5條件代碼
每條 ARM 指令都可以包含一個條件代碼,指定該操作僅在某些標(biāo)志組合成立時才發(fā)生。您可以通過將條件代碼包含在操作碼中來指定條件代碼。它通常出現(xiàn)在操作碼的末尾,但它在基本算術(shù)指令上的可選 S 之前。條件代碼的名稱是基于假設(shè)標(biāo)志是基于 CMP 或 SUBS 指令設(shè)置的。
到目前為止,我們看到的這個條件代碼的唯一實例是 BNE 指令:在這種情況下,我們有一個用于分支的 B 指令,但只有在 Z 標(biāo)志為 0 時才會發(fā)生分支。
但是 ARM 的 ISA 也允許我們將條件代碼應(yīng)用于其他操作碼。例如,ADDEQ 表示如果 Z 標(biāo)志為 1,則執(zhí)行加法。在非分支指令上使用條件代碼的一種常見情況是使用 Euclid 的 GCD 算法計算兩個數(shù)字的最大公約數(shù)。
傳統(tǒng)的匯編語言翻譯只會在分支指令上使用條件代碼。
但是,以下是更短且更有效的翻譯。
由于兩個原因,這更有效。更明顯的是,每次迭代執(zhí)行的指令數(shù)量更少(四個對五個)。但另一個原因來自現(xiàn)代處理器在執(zhí)行當(dāng)前指令時“預(yù)取”下一條指令的事實。但是,由于無法確定下一條指令的位置,因此分支會中斷此過程。第二次翻譯涉及更少的分支指令,因此預(yù)取指令的問題更少。
3存儲(Memory)
我們已經(jīng)了解了如何構(gòu)建執(zhí)行基本數(shù)值計算的匯編程序。我們現(xiàn)在將轉(zhuǎn)向檢查匯編程序如何訪問內(nèi)存。
3.1基本內(nèi)存指令
ARM 支持通過兩條指令 LDR 和 STR 訪問內(nèi)存。LDR 指令從內(nèi)存中加載數(shù)據(jù),STR 將數(shù)據(jù)存儲到內(nèi)存中。每個都有兩個參數(shù)。第一個參數(shù)是數(shù)據(jù)寄存器:對于一條 LDR 指令,加載的數(shù)據(jù)放在這個寄存器中;對于 STR 指令,在該寄存器中找到的數(shù)據(jù)存儲到內(nèi)存中。第二個參數(shù)表示包含正在訪問的內(nèi)存地址的寄存器;它將使用括號中的寄存器名稱寫入。
有關(guān)這些指令如何工作的示例,假設(shè)我們需要一個匯編程序片段,它將整數(shù)添加到數(shù)組中。我們假設(shè) R0 保存數(shù)組的第一個整數(shù)的地址,R1 保存數(shù)組中整數(shù)的個數(shù)。
在這個片段中,我們使用 R4 來保存到目前為止的整數(shù)之和。在 LDR 指令中,我們在 R0 中查找內(nèi)存地址并將在該地址找到的數(shù)據(jù)加載到 R2 中。然后我們將此值添加到 R4 中。然后,我們移動 R0 使其包含數(shù)組中下一個整數(shù)的內(nèi)存地址;我們將 R0 增加四,因為每個整數(shù)消耗四個字節(jié)的內(nèi)存。最后,我們遞減 R1,這是要從數(shù)組中讀取的整數(shù)個數(shù),如果還有整數(shù),我們重復(fù)這個過程。
LDR 和 STR 都加載和存儲 32 位值。還有使用 8 位值、LDRB 和 STRB 的說明;這些主要用于處理字符串。下面是 C 的 strcpy 函數(shù)的實現(xiàn);我們假設(shè) R0 保存目標(biāo)數(shù)組的第一個字符的地址,而 R1 保存源字符串的第一個字符的地址。我們希望繼續(xù)復(fù)制,直到我們復(fù)制終止 NUL 字符(ASCII 0)。
3.2尋址模式
在上一節(jié)的示例中,我們通過將寄存器名稱括在括號中來提供地址。但是 ARM 也允許使用其他幾種方式來指示內(nèi)存地址。每一種這樣的技術(shù)都稱為尋址模式。簡單地命名保存內(nèi)存地址的寄存器的技術(shù)就是一種這樣的尋址模式,稱為寄存器尋址,但還有其他的。
其中之一是縮放的寄存器偏移量,我們在括號中包括一個寄存器、另一個寄存器和一個移位值。為了計算要訪問的內(nèi)存地址,處理器獲取第一個寄存器,并將根據(jù)移位值移位的第二個寄存器添加到它。(括號中提到的寄存器都不會改變值。)當(dāng)訪問知道數(shù)組索引的數(shù)組時,這種尋址模式很有用。我們可以修改之前的例程,將整數(shù)添加到數(shù)組中,以利用這種尋址模式。
對于循環(huán)的每次迭代,我們首先減少循環(huán)索引 R1。然后我們使用縮放的寄存器偏移量檢索數(shù)組條目處的元素:我們使用 R0 作為我們的基數(shù),并將 R1 添加到它左移兩個位置。我們將 R1 左移兩位,使 R1 乘以 4;畢竟,數(shù)組中的每個整數(shù)都是四個字節(jié)長。將加載的值添加到 R4 中,累加總數(shù)后,如果 R1 尚未達到 0,我們重復(fù)循環(huán)。
除了使用不同的尋址模式之外,這個版本的代碼在三個方面與我們的原始實現(xiàn)略有不同。首先,它以相反的順序加載數(shù)組中的數(shù)字——也就是說,它首先加載數(shù)組中的最后一個數(shù)字。其次,R0 在片段的過程中保持不變。最后,它會更快一些,因為它在每次循環(huán)迭代中減少了一條指令。
立即后索引尋址是另一種尋址模式。為了在匯編語言中表示這種模式,我們在括號后面加上逗號和正或負立即數(shù)。在執(zhí)行指令時,處理器仍然會訪問在寄存器中找到的內(nèi)存地址,但在訪問內(nèi)存后,地址寄存器會根據(jù)立即數(shù)增加或減少。
我們的 strcpy 實現(xiàn)是一個有用的示例,其中立即后索引尋址很有用:在我們存儲到 R0 之后,我們希望 R0 在下一次迭代中增加 1;同樣,在我們從 R1 加載之后,我們希望 R1 增加 1。我們可以使用立即后索引尋址來避免我們早期版本的兩個 ADD 指令。
目前ARM處理器支持9種尋址方式,分別是立即數(shù)尋址、寄存器尋址、寄存器偏移尋址、寄存器間接尋址、基址變址尋址、多寄存器尋址、相對尋址、堆棧尋址和塊拷貝尋址。
尋址方式介紹:https://www.cnblogs.com/laojie4321/archive/2012/04/05/2432957.html
對于那些涉及移位的尋址模式,移位技術(shù)與算術(shù)指令(LSL、LSR、ASR、ROR、RRX)一樣。但移位距離不能根據(jù)寄存器:距離必須是立即數(shù)。
3.3. 初始化內(nèi)存
我們經(jīng)常希望保留內(nèi)存來保存程序中的數(shù)據(jù)。為此,我們使用指令:指令匯編器做一些事情,而不是簡單地將匯編語言指令翻譯成相應(yīng)的機器代碼。一種有用的指令是 DCD,它將一個或多個 32 位數(shù)值插入機器代碼輸出。(DCD 神秘地代表定義常量雙字。)
在這個例子中,我們創(chuàng)建了標(biāo)簽 primes,它將對應(yīng)于 2 放入內(nèi)存的地址。在接下來的四個字節(jié)中放置整數(shù) 3,然后是 5,依此類推。
在我們的程序中,我們希望將數(shù)組的地址加載到寄存器中;為此,我們將素數(shù)添加到程序計數(shù)器 PC(與 R15 同義)中。下面的片段將第五個素數(shù) (11) 加載到 R1 中。
另一個值得一提的指令是 DCB,用于將字節(jié)加載到內(nèi)存中。因此,我們可以編寫以下內(nèi)容。
但是,我們只為每個數(shù)字使用一個字節(jié),因此我們只能包含介于 -128 和 127 之間的數(shù)字。我們還可以在列表中包含一個字符串;字符串的每個字符將占用一個字節(jié)的內(nèi)存。
請注意我們?nèi)绾卧谧址蟀?0。沒有這個,字符串不會被 NUL 字符終止。
這里還有一個值得注意的指令是百分號 %。當(dāng)您希望保留一塊內(nèi)存但您不關(guān)心內(nèi)存的初始值時,這很有用。
3.4多寄存器內(nèi)存指令
ARM ISA 還包括允許在同一指令中加載或存儲多個值的指令。LDMIA 指令就是這樣一條指令:它允許從另一個寄存器中指定的地址開始加載到多個寄存器中。在下面的使用示例中,我們將代碼用于添加數(shù)組的整數(shù),并使用 LDMIA 對其進行修改,以便在循環(huán)的每次迭代中處理四個整數(shù)。這種策略允許程序使用更少的指令運行,但代價是更高的復(fù)雜性。
在執(zhí)行上面的 LDMIA 指令時,ARM 處理器在 R0 寄存器中查找地址。它將從該地址開始的四個字節(jié)加載到 R5,接下來的四個字節(jié)加載到 R6,接下來的四個字節(jié)加載到 R7,接下來的四個字節(jié)加載到 R8。同時,R0 前移 16 個字節(jié),因此在下一次迭代中,LDMIA 指令會將接下來的四個字加載到寄存器中。
大括號內(nèi)可以是任何寄存器列表,使用破折號表示寄存器范圍,并使用逗號分隔范圍。因此,指令 LDMIA R0!, { R1-R4, R8, R11-R12 } 將從內(nèi)存中加載 7 個字。寄存器列出的順序并不重要;即使我們寫 LDMIA R0!, { R11-R12, R8, R1-R4 },R1 也會收到從內(nèi)存中加載的第一個字。
在我們的例子中,R0 后面的感嘆號可以省略;如果省略,則地址寄存器不會被指令更改。也就是說,R0 將繼續(xù)指向數(shù)組中的第一個整數(shù)。在上面的示例中,我們希望 R0 發(fā)生變化,使其指向下一個由四個整數(shù)組成的塊以進行下一次迭代,因此我們包含了感嘆號。
另一條指令是 STMIA,它將幾個寄存器存儲到內(nèi)存中。在下面的示例中,我們將數(shù)組中的每個數(shù)字移動到下一個位置;因此,數(shù)組<2,3,5,7>變?yōu)?lt;0,2,3,5>。
請注意 LDMIA 指令如何省略感嘆號,以便不修改 R0。這樣 STMIA 將存儲到剛剛加載到寄存器中的相同地址范圍內(nèi)。STMIA 指令帶有感嘆號,因為必須修改 R0 以準備循環(huán)的下一次迭代。
ARM 處理器包括多重加載和多重存儲指令的四種變體;LDM 和 STM 縮寫必須始終表示這四種變體之一。
LDMIA, STMIA 之后遞增:我們從命名地址開始加載,并不斷增加地址。
LDMIB, STMIB 之前的增量:我們開始從比命名地址多四個的地址開始加載,并不斷增加地址。LDMDA, STMDA 減量后:我們從命名地址開始加載并進入遞減地址。
LDMDB、STMDB 遞減前:我們從比指定地址少四個開始加載到遞減地址。
在所有四種模式中,編號最高的寄存器始終對應(yīng)于內(nèi)存中的最高地址。因此,指令 LDMDA R0, { R1-R4 } 會將 R4 放入由 R0 命名的地址,將 R3 放入 R0 - 4,依此類推。
正如我們將在研究子程序時看到的那樣,當(dāng)我們想將一塊未使用的內(nèi)存用作堆棧時,不同的變體特別有用。
部分參考文獻
https://www.cnblogs.com/laojie4321/archive/2012/04/05/2432957.html
http://aratxa.ii.uam.es/~gdrivera/sed/docs/ARMBook.pdf
https://students.mimu.edu.pl/~zbyszek/asm/arm/asm_guide.pdf