本篇記錄了《匯編語言:基于X86處理器》第13章 復習題和編程練習的學習筆記。
13.6 復習題
1.當匯編過程被高級語言程序調用時,主調程序與被調過程是否應使用相同的內存模式?
答:主調程序與被調過程使用的內存模式必須相同。
2.C 和 C++程序調用匯編過程時,為什么區分大小寫是很重要的?
答:C和C++編譯器編譯時是區分大小寫的,所以如果調用匯編過程必須區分大小寫,這樣才能正確調用相應的匯編過程。
3.一種編程語言的調用規范是否包括了過程對某些寄存器的保存規定?
答:是的
4.(是/否):EVEN 和ALIGN偽指令是否都能用于內嵌匯編代碼?
答:是
5.(是/否):OFFSET運算符是否能用于內嵌匯編代碼?
答:否
6.(是/否):內嵌匯編代碼中,DW和DUP運算符是否都能用于變量定義?
答:否
7.使用 fastcall調用規范時,若內嵌匯編代碼修改了寄存器會出現什么情況?
答:_fastcall會使用編譯器用寄存器來傳遞參數,會引起寄存器沖突,使用程序會結果錯亂。
8.不使用OFFSET運算符,是否還有其他方法能把變量偏移量送入變址寄存器?
答:要以使用LEA指令得到變量偏移地址。例如 :lea esi,buffer ;將buffer的偏移地址送入ESI.
9.對 32位整數數組使用 LENGTH運算符,其返回值是多少?
答:LENGTH
的返回值是數組的元素個數(與每個元素的大小無關 )
10.對長整型數組使用SIZE運算符,其返回值是多少?
答:返回值是元素的個數*單個元素的大小。LENGTH array * TYPE long
11.標準C printf()函數的有效匯編PROTO 聲明是怎樣的?
答:printf PROTO C, pString:PTR BYTE, args:VARARG
12.調用如下C語言函數,實參x是最先入棧還是最后入棧?
void MySub( x, y, z);
答:X是最后入棧,調用C語言函數時,是從右向左逆向入棧。
13.過程被 C++調用時,其外部聲明使用的“C”說明符有什么作用?
答:防止c++的名稱修飾。從匯編語言程序員的角度來看,名稱修飾存在的問題是:C++編譯器讓鏈接器去找的是修飾過的名稱,而不是生成可執行文件時的原始名稱。
14.C++調用外部匯編過程時,為什么名稱修飾是重要的?
答:C++編譯器編譯代碼時會對函數名稱進行修飾,例如:sub()函數編譯時可能變成了_sub()函數,如果不使用名稱修飾就會找不到對應的原始名稱。
15.搜索互聯網,用簡表列出C/C++編譯器使用的優化技巧。
答:
1. 高級優化
內聯展開(Inline Expansion)
將小函數調用替換為函數體本身,減少調用開銷(如-finline-functions
)。
循環展開(Loop Unrolling)
減少循環控制開銷,通過重復循環體(如#pragma unroll
或-funroll-loops
)。
常量傳播(Constant Propagation)
將常量表達式替換為計算結果(如int x = 3 * 5;
→ int x = 15;
)。
死代碼消除(Dead Code Elimination)
刪除不可達的代碼(如未使用的變量或條件分支)。
函數返回值優化(RVO/NRVO)
避免臨時對象的復制(直接構造返回值到目標內存)。
2. 循環優化
循環不變代碼外提(Loop Invariant Code Motion)
將循環內不變的表達式移到循環外。
循環融合(Loop Fusion)
合并相鄰的循環以減少迭代次數。
循環分塊(Loop Tiling)
優化內存訪問局部性(尤其對多維數組)。
3. 內存與指針優化
別名分析(Alias Analysis)
推斷指針是否指向同一內存區域(如restrict
關鍵字)。
標量替換(Scalar Replacement)
將數組元素替換為局部變量(減少內存訪問)。
寫緩沖優化(Write Buffering)
合并多次內存寫入操作。
4. 指令級優化
指令調度(Instruction Scheduling)
重新排列指令以避免CPU流水線停頓。
自動向量化(Auto-Vectorization)
使用SIMD指令(如SSE/AVX)并行化計算(-mavx
)。
分支預測優化(Branch Prediction)
通過重排代碼提高分支預測命中率(如likely/unlikely
宏)。
5. 鏈接時優化(LTO, Link-Time Optimization)
跨編譯單元優化(如-flto
),允許內聯和刪除未使用的全局函數。
6. 其他常見優化
尾調用優化(Tail Call Optimization)
將遞歸尾調用轉為循環(避免棧溢出)。
公共子表達式消除(CSE)
重復計算的表達式只計算一次。
強度削減(Strength Reduction)
用低成本操作替換高成本操作(如乘法→加法)。
編譯器標志示例(GCC/Clang)
-O1
:基礎優化(如常量傳播、死代碼消除)。
-O2
:激進優化(包括向量化、循環展開)。
-O3
:最高級優化(可能增加代碼體積)。
-Os
:優化代碼大小。
-Ofast
:激進優化,忽略嚴格標準合規性。
注意事項
調試與優化沖突:高優化級別可能導致調試信息不準確(如變量被優化掉)。
未定義行為(UB):依賴UB的代碼可能被激進優化破壞(如指針越界)。
性能權衡:某些優化(如循環展開)可能增加代碼體積,需根據場景選擇。
編譯器通過組合這些技術,在保證語義一致性的前提下最大化性能。實際效果可通過反匯編(objdump -d
或編譯器資源管理器)驗證。
13.7編程練習
**1.數組與整數相乘
編寫匯編子程序,實現一個雙字數組與一個整數的乘法。編寫C/C++測試程序,新建數組并將其傳遞給子程序,再輸出運算后的結果數組。
頭文件
#pragma once // 防止頭文件被重復包含, 非標準(但廣泛支持)
//ArrayMul.h 對應的匯編語言文件ArrayMul.asm C++測試文件13.7_1.cpp extern "C" {void ArrayMul(int n, int array[], unsigned count);// Assembly language module
}
匯編語言實現文件
;ArrayMul函數 ArrayMul.asm C++測試文件13.7_1.cpp .586
.model flat, C
ArrayMul PROTO,intVal:DWORD, arrayPtr:PTR DWORD, count:DWORD.code
;數組乘以同一個整數。返回:無
ArrayMul PROC USES ecx esi edi,intVal:DWORD, arrayPtr:PTR DWORD, count:DWORDmov ecx, count ;數組大小mov esi, arrayPtr ;數組指針mov edi, 0 L1: mov eax, [esi+edi*4]mul intVal ;乘以同一個整數mov [esi+edi*4], eaxinc edi ;下一個數loop L1ret
ArrayMul ENDP
END
C++測試文件
//13.7_1.cpp 13.7編譯練習 **1.數組與整數相乘
//編寫匯編子程序,實現一個雙字數組與一個整數的乘法。
//編寫C / C++測試程序,新建數組并將其傳遞給子程序,再輸出運算后的結果數組。#include <iostream>
#include <time.h>
#include "ArrayMul.h"
using namespace std;int main()
{const int ARRAY_SIZE = 10;int array[ARRAY_SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};for (unsigned i = 0; i < ARRAY_SIZE; i++)printf("%d\t", array[i]);cout << endl;int intVal;cout << "Enter an integer value: ";cin >> intVal;ArrayMul(intVal, array, ARRAY_SIZE);for (unsigned i = 0; i < ARRAY_SIZE; i++)printf("%d\t", array[i]);cout << endl;return 0;
}
運行調試:
***2.最長遞增序列
編寫匯編子程序,接收兩個輸人參數:數組偏移量和數組大小。子程序返回數組中最長的遞增序列中整數值的個數。比如,數組如下所示,則最長的嚴格遞增序列開始于索引值為3的元素、序列長度為4{14,17、26、42}:
[-5,10,20,14,17,26,42、22,19,-5]
編寫 C/C++測試程序調用該子程序,測試程序實現的操作包括:新建數組、傳遞參數、輸出子程序的返回值。
頭文件:LongestSelfIncSeq.h
#pragma once // 防止頭文件被重復包含, 非標準(但廣泛支持)
//LongestSelfIncSeq.h 對應的匯編語言文件LongestSelfIncSeq.asm C++測試文件13.7_2.cpp extern "C" {int LongestSelfIncSeq(int array[], unsigned count);// Assembly language module
}
匯編實現文件: LongestSelfIncSeq.asm
;LongestSelfIncSeq函數 LongestSelfIncSeq.asm C++測試文件13.7_2.cpp .586
.model flat, C
LongestSelfIncSeq PROTO,arrayPtr:PTR DWORD, count:DWORD.code
;查找數組中最長的遞增序列中整數值的個數。返回:eax為最長遞增序列的個數
LongestSelfIncSeq PROC USES ecx esi edi,arrayPtr:PTR DWORD, count:DWORDlocal counter:DWORDlocal position: dwordmov counter, 0mov esi, arrayPtr ;數組的起始地址mov edi, arrayPtradd edi, TYPE DWORD ;第2個元素 用以相臨元素的比較是否自增mov ecx, count ;數組大小mov edx, 0 ;設置計數器L1: mov eax, [esi]mov ebx, [edi]cmp ebx, eax ;比較相臨兩個元素是否遞增jl L2 ;ebx < eax 有符號比較:小于跳轉L2inc edx ;否則ebx > eax, 遞增序列的個數自增jmp L3L2: ;此處EDX中的當前計數器與counter中先前保存的值進行了比較mov eax, countercmp edx, eaxjb L3 ;無符號比較:小于跳轉L3,如果EDX中的當前值大于之前保存在 counter中的值,則EDX將保存在計數器中mov counter, edx ;保存最長遞增序列的長度mov position, esi ;保存最長序列最后一個成員的索引mov edx, 0
L3:add esi, TYPE DWORD ;下一個元素add edi, TYPE DWORDloop L1mov eax, counterinc eaxret
LongestSelfIncSeq ENDP
END
C++測試文件:13.7_2.cpp
//13.7_2.cpp 13.7編譯練習 ***2.最長遞增序列
//編寫匯編子程序,接收兩個輸人參數:數組偏移量和數組大小。子程序返回數組中最長的遞增序列中整數值的個數。
//比如,數組如下所示,則最長的嚴格遞增序列開始于索引值為3的元素、序列長度為4{ 14,17、26、42 } :
// [-5,10,20,14,17,26,42、22,19, - 5]
//編寫 C / C++測試程序調用該子程序,測試程序實現的操作包括 : 新建數組、傳遞參數、輸出子程序的返回值。#include <iostream>
#include <time.h>
#include "LongestSelfIncSeq.h"using namespace std;int main()
{int array[10] = { -5, 10, 20, 14, 17, 26, 42, 22, 19, -5 };int num = LongestSelfIncSeq(array, sizeof(array) / 4);printf("Reslut is: %d\n", num);return 0;
}
運行調試:
**3.三個數組求和
編寫匯編子程序。接收三個同樣大小數組的偏移量。將第二個和第三個數組加到第一個數組上。子程序返回時,第一個數組包含結果數值。編寫 C/C++測試程序,新建數組并將其傳遞給子程序,再顯示第一個數組的內容。
頭文件:ArraySum.h
#pragma once // 防止頭文件被重復包含, 非標準(但廣泛支持)
//ArraySum.h 對應的匯編語言文件ArraySum.asm C++測試文件13.7_3.cpp extern "C" {void ArraySum(int array1[], int array2[], int array3[], unsigned count);// Assembly language module
}
匯編文件ArraySum.asm
;ArraySum函數 ArraySum.asm C++測試文件13.7_3.cpp .586
.model flat, C
ArraySum PROTO,arrayPtr1:PTR DWORD, arrayPtr1:PTR DWORD, arrayPtr3:PTR DWORD, count:DWORD.code
;把第2個數組與第3個數組相加,和放在第1個數組。返回:無
ArraySum PROC USES ecx esi edi ebx edx,arrayPtr1:PTR DWORD, arrayPtr2:PTR DWORD, arrayPtr3:PTR DWORD, count:DWORDmov ecx, count ;數組大小mov esi, arrayPtr2 ;數組指針mov edi, arrayPtr3mov edx, arrayPtr1mov ebx, 0L1: mov eax, [esi + ebx*4]add eax, [edi + ebx*4] ;相加mov [edx + ebx*4], eaxinc ebx ;下一個數loop L1ret
ArraySum ENDP
END
C++測試文件:
//13.7_3.cpp 13.7編譯練習 **3.三個數組求和
//編寫匯編子程序。接收三個同樣大小數組的偏移量。將第二個和第三個數組加到第一個數組上。
//子程序返回時,第一個數組包含結果數值。編寫 C / C++測試程序,新建數組并將其傳遞給子程序,
//再顯示第一個數組的內容。#include <iostream>
#include <time.h>
#include "ArraySum.h"
using namespace std;int main()
{const int ARRAY_SIZE = 10;int array1[ARRAY_SIZE] = { 0 };int array2[ARRAY_SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};int array3[ARRAY_SIZE] = { 11,12, 13, 14, 15, 16, 17, 18, 19, 20 };cout << "array 2: " << endl;for (unsigned i = 0; i < ARRAY_SIZE; i++)printf("%d\t", array2[i]);cout << endl;cout << "array 3: " << endl;for (unsigned i = 0; i < ARRAY_SIZE; i++)printf("%d\t", array3[i]);cout << endl;cout << "array1 = array2 + array3: " << endl;ArraySum(array1, array2, array3, ARRAY_SIZE);for (unsigned i = 0; i < ARRAY_SIZE; i++)printf("%d\t", array1[i]);cout << endl;cout << endl;return 0;
}
運行調試:
***4.質數程序
編寫匯編過程實現如下功能:若傳遞給EAX 的 32 位整數為質數,則返回1;若 EAX 為非質數,則返回 0。要求從高級語言程序調用該過程。由用戶輸入一組整數,對每個數值,程序都要顯示一條信息以示該數是否為質數。建議:第一次調用該過程時,使用厄拉多塞過濾算法(Sieve ofEratosthenes)初始化布爾數組。
頭文件:isPrime.h
#pragma once // 防止頭文件被重復包含, 非標準(但廣泛支持)
//isPrime.h 對應的匯編語言文件isPrime.asm C++測試文件13.7_4.cpp extern "C" {int isPrime(unsigned intVal);// Assembly language module
}
匯編語言過程實現文件:isPrime.asm
;isPrime函數 isPrime.asm C++測試文件13.7_4.cpp .586
.model flat, C
isPrime PROTO,intVal:DWORD .code
;判斷是否為素數。返回:是素數eax為1, 不是素數eax為0
isPrime PROC USES ecx edx,intVal:DWORDmov eax, intValcmp eax, 1je isNotcmp eax, 2 ;2是最小的素數je stopmov ecx, intValsub ecx, 2 ;1和本身略過mov ebx, intValdec ebxL1: mov edx, 0mov eax, intValdiv ebx ;商在eax中,余數在edx中cmp edx, 0je isNotcmp ecx, 1je stopdec ebxloop L1
stop:mov eax, 1ret
isNot:mov eax, 0ret
isPrime ENDP
END
C++測試文件:13.7_4.cpp
//13.7_4.cpp 13.7編譯練習 ***4.質數程序
//編寫匯編過程實現如下功能:若傳遞給EAX 的 32 位整數為質數,則返回1; 若 EAX 為非質數,則返回 0。
//要求從高級語言程序調用該過程。由用戶輸入一組整數,對每個數值,程序都要顯示一條信息以示該數是否為質數。
//建議:第一次調用該過程時,使用厄拉多塞過濾算法(Sieve ofEratosthenes)初始化布爾數組。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include "isPrime.h"
using namespace std;int main()
{int array[6] = { 0 };printf("Please input six integers:\n");for (unsigned i = 0; i < 6; i++)scanf("%d", &array[i]);printf("\n");for (unsigned i = 0; i < 6; i++){if (isPrime(array[i]))printf("%d is prime\n", array[i]);elseprintf("%d is not prime\n", array[i]);}printf("\n");return 0;
}
運行調試:
*5.LastindexOf過程
修改13.3.1 節的 IndexOf 過程。將新函數命名為LastlndexOf,使其從數組末尾開始反向搜索。遇到第一個匹配值就返回其索引,否則即為未發現匹配值,返回-1。
.h頭文件
#pragma once // 防止頭文件被重復包含, 非標準(但廣泛支持)
// indexof.h 對應的匯編語言文件IndexOf.asm C++測試文件13.7_5.cpp extern "C" {long IndexOf(long n, long array[], unsigned count);// Assembly language module
}
匯編實現文件
;IndexOf函數 IndexOf.asm C++測試文件13.7_5.cpp .586
.model flat, C
IndexOf PROTO,srchVal:DWORD, arrayPtr:PTR DWORD, count:DWORD.code
;---------------------------------------------------------------
IndexOf PROC USES ecx esi edi,srchVal:DWORD, arrayPtr:PTR DWORD, count:DWORD
;
;對32位整數數組執行線性搜索——從后往前搜索
;尋找指定數值。如果發現匹配數值
;用EAX返回該數值的索引位置
;否則,EAX返回-1.
;----------------------------------------------------------------NOT_FOUND = -1mov eax, srchVal ;搜索數值mov ecx, count ;數組大小mov esi, arrayPtr ;數組指針mov edi, count ;索引到最后dec edi ;最后一個元素的索引(數組從0開始)L1: cmp [esi+edi*4], eaxje founddec edi ;從后往前索引loop L1notFound:mov eax, NOT_FOUNDjmp short exitfound:mov eax, ediexit:ret
IndexOf ENDP
END
C++測試文件
//13.7_5.cpp 13.7編譯練習 *5.LastindexOf過程
//修改13.3.1 節的 IndexOf 過程。將新函數命名為LastlndexOf,使其從數組末尾開始反向搜索。
//遇到第一個匹配值就返回其索引,否則即為未發現匹配值,返回 - 1。
#include <iostream>
#include <time.h>
#include "indexof.h"
using namespace std;int main(){//用偽隨機數填充數組。const unsigned ARRAY_SIZE = 100000;const unsigned LOOP_SIZE = 100000;const char* boolstr[] = { "false","true" };long array[ARRAY_SIZE];for (unsigned i = 0; i < ARRAY_SIZE; i++)array[i] = rand() % 50000;long searchVal;time_t startTime, endTime;cout << "Enter an integer value to find: ";cin >> searchVal;cout << "Please wait...\n";//測試匯編函數time(&startTime);long index = 0;for (unsigned n = 0; n < LOOP_SIZE; n++)index = IndexOf( searchVal, array, ARRAY_SIZE );bool found = index != -1;time(&endTime);cout << "Elapsed ASM time: " << long(endTime - startTime)<< " seconds. Found = " << boolstr[found] << endl;return 0;
}
運行調試: