汇编语言数据类型以及数据定义详解
匯編器識別一組基本的內部數據類型(intrinsic data type),按照數據大小(字節、字、雙字等等)、是否有符號、是整數還是實數來描述其類型。這些類型有相當程度的重疊,例如,DWORD 類型(32 位,無符號整數)就可以和 SDWORD 類型(32 位,有符號整數)相互交換。
?
可能有人會說,程序員用 SDWORD 告訴讀程序的人,這個值是有符號的,但是,對于匯編器來說這不是強制性的。匯編器只評估操作數的大小。因此,舉例來說,程序員只能將 32 位整數指定為 DWORD、SDWORD 或者 REAL4 類型。
下表給出了全部內部數據類型的列表,有些表項中的 IEEE 符號指的是 IEEE 計算機學會出版的標準實數格式。
| BYTE | 8 位無符號整數,B 代表字節 |
| SBYTE | 8 位有符號整數,S 代表有符號 |
| WORD | 16 位無符號整數 |
| SWORD | 16 位有符號整數 |
| DWORD | 32 位無符號整數,D 代表雙(字) |
| SDWORD | 32 位有符號整數,SD 代表有符號雙(字) |
| FWORD | 48 位整數(保護模式中的遠指針) |
| QWORD | 64 位整數,Q 代表四(字) |
| TBYTE | 80 位(10 字節)整數,T 代表 10 字節 |
| REAL4 | 32 位(4 字節)IEEE 短實數 |
| REAL8 | 64 位(8 字節)IEEE 長實數 |
| REAL10 | 80 位(10 字節)IEEE 擴展實數 |
數據定義語句
數據定義語句(data definition statement)在內存中為變量留岀存儲空間,并賦予一個可選的名字。數據定義語句根據內部數據類型(上表)定義變量。
數據定義語法如下所示:
[name] directive initializer [,initializer]…
下面是數據定義語句的一個例子:
count DWORD 12345
其中:
- 名字:分配給變量的可選名字必須遵守標識符規范。
- 偽指令:數據定義語句中的偽指令可以是 BYTE、WORD、DWORD、SBTYE、SWORD 或其他在上表中列出的類型。此外,它還可以是傳統數據定義偽指令,如下表所示。
?
| DB | 8位整數 | DQ | 64 位整數或實數 |
| DW | 16 位整數 | DT | 定義 80 位(10 字節)整數 |
| DD | 32 位整數或實數? | ? | ? |
數據定義中至少要有一個初始值,即使該值為 0。其他初始值,如果有的話,用逗號分隔。對整數數據類型而言,初始值(initializer)是整數常量或是與變量類型,如 BYTE 或 WORD 相匹配的整數表達式。
如果程序員希望不對變量進行初始化(隨機分配數值),可以用符號 ? 作為初始值。所有初始值,不論其格式,都由匯編器轉換為二進制數據。 初始值 0011 0010b、32h 和 50d 都具有相同的二進制數值。
向 AddTwo 程序添加一個變量
前面《整數加減法》一節中介紹了 AddTwo 程序,現在創建它的一個新版本,并稱為 AddTwoSum。這個版本引入了變量 sum,它出現在完整的程序清單中:
;AddTowSum.asm .386 .model flat,stdcall .stack 4096 ExitProcess PROTO, dwExitCode:DWORD .data sum DWORD 0 .code main PROC mov eax,5 add eax,6 mov sum,eax INVOKE ExitProcess,0 main ENDP END main可以在第 13 行設置斷點,每次執行一行,在調試器中單步執行該程序。執行完第 15 行后,將鼠標懸停在變量 sum 上,查看其值。或者打開一個 Watch 窗口,打開過程如下:在 Debug 菜單中選擇 Windows(在調試會話中),選擇 Watch,并在四個可用選項(Watch1,Watch2,Watch3 或 Watch4)中選擇一個。然后,用鼠標高亮顯示 sum 變量,將其拖拉到 Watch 窗口中。下圖展示了一個例子,其中用大箭頭指出了執行第 15 行后,sum 的當前值。
定義 BYTE 和 SBYTE 數據
BYTE(定義字節)和 SBYTE(定義有符號字節)為一個或多個無符號或有符號數值分配存儲空間。每個初始值在存儲時,都必須是 8 位的。例如:
value1 BYTE 'A' ;字符常量 value2 BYTE 0 ;最小無符號字節 value3 BYTE 255 ;最大無符號字節 value4 SBYTE -128 ;最小有符號字節 value5 SBYTE +127 ;最大有符號字節問號(?)初始值使得變量未初始化,這意味著在運行時分配數值到該變量:
value6 BYTE ?
可選名字是一個標號,標識從變量包含段的開始到該變量的偏移量。比如,如果 value1? 在數據段偏移量為 0000 處,并在內存中占一個字節,則 value2 就自動處于偏移量為 0001 處:
value1 BYTE 10h
value2 BYTE 20h
DB 偽指令也可以定義有符號或無符號的 8 位變量:
val1 DB 255? ? ;無符號字節
val2 DB -128? ;有符號字節
1) 多初始值
如果同一個數據定義中使用了多個初始值,那么它的標號只指出第一個初始值的偏移量。在下面的例子中,假設 list 的偏移量為 0000。那么,數值 10 的偏移量就為 0000, 20 的偏移量為 0001,30 的偏移量為 0002,40 的偏移量為 0003:
list BYTE 10,20,30,40
下圖給出了字節序列 list,顯示了每個字節及其偏移量。
并不是所有的數據定義都要用標號。比如,在 list 后面繼續添加字節數組,就可以在下一行定義它們:
list BYTE 10,20,30,40 BYTE 50,60,70,80 BYTE 81,82,83,84在單個數據定義中,其初始值可以使用不同的基數。字符和字符串常量也可以自由組合。在下面的例子中,list1 和 list2 有相同的內容:
list1 BYTE 10, 32, 41h, 00100010b list2 BYTE 0Ah, 20h, 'A', 22h2) 定義字符串
定義一個字符串,要用單引號或雙引號將其括起來。最常見的字符串類型是用一個空字節(值為0)作為結束標記,稱為以空字節結束的字符串,很多編程語言中都使用這種類型的字符串:
greeting1 BYTE "Good afternoon",0 greeting2 BYTE 'Good night',0每個字符占一個字節的存儲空間。對于字節數值必須用逗號分隔的規則而言,字符串是一個例外。如果沒有這種例外,greeting1 就會被定義為:
greeting1 BYTE 'G', 'o', 'o', 'd'….etc.
這就顯得很冗長。一個字符串可以分為多行,并且不用為每一行都添加標號:
greeting1 BYTE "Welcome to the Encryption Demo program " BYTE "created by Kip Irvine.",0dh, 0ah BYTE "If you wish to modify this program, please " BYTE "send me a copy.",0dh,0ah,0十六進制代碼 0Dh 和 0Ah 也被稱為 CR/LF (回車換行符)或行結束字符。在編寫標準輸出時,它們將光標移動到當前行的下一行的左側。
行連續字符()把兩個源代碼行連接成一條語句,它必須是一行的最后一個字符。下面的語句是等價的:
greeting1 BYTE "Welcome to the Encryption Demo program "
和
greeting1?
BYTE "Welcome to the Encryption Demo program "
3) DUP 操作符
DUP 操作符使用一個整數表達式作為計數器,為多個數據項分配存儲空間。在為字符串或數組分配存儲空間時,這個操作符非常有用,它可以使用初始化或非初始化數據:
BYTE 20 DUP ( 0 ) ;20 個字節,值都為 0 BYTE 20 DUP ( ? ) ;20 個字節,非初始化 BYTE 4 DUP ( "STACK" ) ; 20 個字節:定義 WORD 和 SWORD 數據
WORD(定義字)和 SWORD(定義有符號字)偽指令為一個或多個 16 位整數分配存儲空間:
word1 WORD 65535 ;最大無符號數 word2 SWORD -32768 ;最小有符號數 word3 WORD ? ;未初始化,無符號也可以使用傳統的 DW 偽指令:
val1 DW 65535 ;無符號 val2 DW -32768 ;有符號16 位字數組通過列舉元素或使用 DUP 操作符來創建字數組。下面的數組包含了一組數值:
myList WORD 1,2,3,4,5
下圖是一個數組在內存中的示意圖,假設 myList 起始位置偏移量為0000。由于每個數值占兩個字節,因此其地址遞增量為 2。
DUP 操作符提供了一種方便的方法來聲明數組:
array WORD 5 DUP (?) ; 5 個數值,未初始化
定義 DWORD 和 SDWORD 數據
DWORD(定義雙字)和 SDWORD(定義有符號雙字)偽指令為一個或多個 32 位整數分配存儲空間:
val1 DWORD 12345678h ;無符號 val2 SDWORD -2147483648 ;有符號 val3 DWORD 20 DUP (?) ;無符號數組傳統的 DD 偽指令也可以用來定義雙字數據:
val1 DD 12345678h ;無符號 val2 DD -2147483648 ;有符號DWORD 還可以用于聲明一種變量,這種變量包含的是另一個變量的 32 位偏移量。如下所示,pVal 包含的就是 val3 的偏移量:
pVal DWORD val3
32 位雙字數組
現在定義一個雙字數組,并顯式初始化它的每 一個值:
myList DWORD 1,2,3,4,5
下圖給岀了這個數組在內存中的示意圖,假設 myList 起始位置偏移量為 0000,偏移量增量為 4。
定義 QWORD 數據
QWORD(定義四字)偽指令為 64 位(8 字節)數值分配存儲空間:
quad1 QWORD 1234567812345678h
傳統的 DQ 偽指令也可以用來定義四字數據:
quad1 DQ 1234567812345678h
定義壓縮 BCD(TBYTE)數據
Intel 把一個壓縮的二進制編碼的十進制(BCD, Binary Coded Decimal)整數存放在一個 10 字節的包中。每個字節(除了最高字節之外)包含兩個十進制數字。在低 9 個存儲字節中,每半個字節都存放了一個十進制數字。最高字節中,最高位表示該數的符號位。如果最高字節為 80h,該數就是負數;如果最高字節為 00h,該數就是正數。整數的范圍是 -999 999 999 999 999 999 到 +999 999 999 999 999 999。
示例下表列出了正、負十進制數 1234 的十六進制存儲字節,排列順序從最低有效字節到最高有效字節:
| +1234 | 34 12 00 00 00 00 00 00 00 00 |
| -1234 | 34 12 00 00 00 00 00 00 00 80 |
MASM 使用 TBYTE 偽指令來定義壓縮 BCD 變量。常數初始值必須是十六進制的,因為,匯編器不會自動將十進制初始值轉換為 BCD 碼。下面的兩個例子展示了十進制 數 -1234 有效和無效的表達方式:
intVal TBYTE 800000000000001234h ;有效 intVal TBYTE -1234 ;無效第二個例子無效的原因是 MASM 將常數編碼為二進制整數,而不是壓縮 BCD 整數。
如果想要把一個實數編碼為壓縮 BCD 碼,可以先用 FLD 指令將該實數加載到浮點寄存器堆棧,再用 FBSTP 指令將其轉換為壓縮 BCD 碼,該指令會把數值舍入到最接近的整數:
.data posVal REAL8 1.5 bcdVal TBYTE ? .code fid posVal ;加載到浮點堆棧 fbstp bcdVal ;向上舍入到 2,壓縮 BCD 碼值如果 posVal 等于 1.5,結果 BCD 值就是 2。
定義浮點類型
REAL4 定義 4 字節單精度浮點變量。REAL8 定義 8 字節雙精度數值,REAL10 定義 10 字節擴展精度數值。每個偽指令都需要一個或多個實常數初始值:
rVal1 REAL4 -1.2 rVal2 REAL8 3.2E-260 rVal3 REAL10 4.6E+4096 ShortArray REAL4 20 DUP(0.0)下表描述了標準實類型的最少有效數字個數和近似范圍:
| 短實數 | 6 | 1.18x 10-38?to 3.40 x 1038 |
| 長實數 | 15 | 2.23 x 10-308?to 1.79 x 10308 |
| 擴展精度實數 | 19 | 3.37 x 10-4932?to 1.18 x 104932 |
DD、DQ 和 DT 偽指令也可以定義實數:
rVal1 DD -1.2 ;短實數 rVal2 DQ 3.2E-260 ;長實數 rVal3 DT 4.6E+4096 ;擴展精度實數MASM 匯編器包含了諸如 wal4 和 real8 的數據類型,這些類型表明數值是實數。更準確地說,這些數值是浮點數,其精度和范圍都是有限的。從數學的角度來看,實數的精度和大小是無限的。
變量加法程序
到目前為止,本節的示例程序實現了存儲在寄存器中的整數加法。現在已經對如何定義數據有了一些了解,那么可以對同樣的程序進行修改,使之實現三個整數變量相加,并將和數存放到第四個變量中。
;AddTowSum.asm .386 .model flat,stdcall .stack 4096 ExitProcess PROTO, dwExitCode:DWORD .data firstval DWORD 20002000h secondval DWORD 11111111h thirdval DWORD 22222222h sum DWORD 0 .code main PROC mov eax,firstval add eax,secondval add eax,thirdval mov sum,eax INVOKE ExitProcess,0 main ENDP END main注意,已經用非零數值對三個變量進行了初始化(9?11 行)。16?18 行進行變量相加。x86 指令集不允許將一個變量直接與另一個變量相加,但是允許一個變量與一個寄存器相加。這就是為什么 16?17 行用 EAX 作累加器的原因:
mov eax,firstval
add eax,secondval
第 17 行之后,EAX 中包含了 firstval 和 secondval 之和。接著,第 18 行把 thirdval 加到 EAX 中的和數上:
add eax,thirdval
最后,在第 19 行,和數被復制到名稱為 sum 的變量中:
mov sum,eax
作為練習,鼓勵大家在調試會話中運行本程序,并在每條指令執行后檢查每個寄存器。最終和數應為十六進制的 53335333。
在調試會話過程中,如果想要變量顯示為十六進制,則按下述步驟操作:鼠標在變量或寄存器上懸停 1 秒,直到一個灰色矩形框出現在鼠標下。右鍵點擊該矩形框,在彈出菜單中選擇 Hexadecimal Display。
小端順序
x86 處理器在內存中按小端(little-endian)順序(低到高)存放和檢索數據。最低有效字節存放在分配給該數據的第一個內存地址中,剩余字節存放在隨后的連續內存位置中。考慮一個雙字 12345678h。如果將其存放在偏移量為 0000 的位置,則 78h 存放在第一個字節,56h 存放在第二個字節,余下的字節存放地址偏移量為 0002 和 0003,如下圖所示。
其他有些計算機系統采用的是大端順序(高到低)。 下圖展示了 12345678h 從偏移量 0000 開始的大端順序存放。
聲明未初始化數據
.DATA ? 偽指令聲明未初始化數據。當定義大量未初始化數據時,.DATA ? 偽指令減少了編譯程序的大小。例如,下述代碼是有效聲明:
.data smallArray DWORD 10 DUP (0) ;40 個字節 .data? bigArray DWORD 5000 DUP ( ? ) ;20 000 個字節,未初始化而另一方面,下述代碼生成的編譯程序將會多岀 20 000 個字節:
.data smallArray DWORD 10 DUP ( 0 ) ; 40 個字節 bigArray DWORD 5000 DUP ( ? ) ; 20 000 個字節代碼與數據混合匯編器允許在程序中進行代碼和數據的來回切換。比如,想要聲明一個變量,使其只能在程序的局部區域中使用。下述示例在兩個代碼語句之間插入了一個名為 temp 的變量:
.code mov eax,ebx .data temp DWORD ? .code mov temp,eax盡管 temp 聲明的出現打斷了可執行指令流,MASM 還是會把 temp 放在數據段中,并與保持編譯的代碼段分隔開。然而同時,混用 .code 和 .data 偽指令會使得程序變得難以閱讀。
下一篇:等號偽指令
強力推薦閱讀文章
年薪40+W的大數據開發【教程】,都在這兒!
總結
以上是生活随笔為你收集整理的汇编语言数据类型以及数据定义详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 汇编器以及汇编流程
- 下一篇: vsftpd的虚拟账户配置