1. 程式人生 > >CSAPP深入理解計算機系統(第二版)第三章家庭作業答案

CSAPP深入理解計算機系統(第二版)第三章家庭作業答案

《深入理解計算機系統(第二版)》CSAPP 第三章 家庭作業

這一章介紹了AT&T的彙編指令 比較重要 本人完成了《深入理解計算機系統(第二版)》(以下簡稱CSAPP)第三章的家庭作業,並與網上的一些答案進行了對比修正。

感謝博主summerhust的整理,以下貼出AT&T常用匯編指令

AT&T常用匯編指令

資料傳送指令

指令 效果 描述
movl S,D D <-- S 傳雙字
movw S,D D <-- S 傳字
movb S,D D <-- S 傳位元組
movsbl S,D D <-- 符號擴充套件S 符號位填充(位元組->雙字)
movzbl S,D D <-- 零擴充套件S 零填充(位元組->雙字)
pushl S R[%esp] <-- R[%esp] – 4;M[R[%esp]] <-- S 壓棧
popl D D <-- M[R[%esp]];R[%esp] <-- R[%esp] + 4; 出棧

算數和邏輯操作地址:

指令 效果 描述
leal S,D D = &S movl地版,S地址入D,D僅能是暫存器
incl D D++ 加1
decl D D-- 減1
negl D D = -D 取負
notl D D = ~D 取反
addl S,D D = D + S
subl S,D D = D – S
imull S,D D = D*S
xorl S,D D = D ^ S 異或
orl S,D D = D | S
andl S,D D = D & S
sall k,D D = D << k 左移
shll k,D D = D << k 左移(同sall)
sarl k,D D = D >> k 算數右移
shrl k,D D = D >> k 邏輯右移

特殊算術操作:

指令 效果 描述
imull S R[%edx]:R[%eax] = S * R[%eax] 有符號64位乘
mull S R[%edx]:R[%eax] = S * R[%eax] 無符號64位乘
cltd S R[%edx]:R[%eax] = 符號位擴充套件R[%eax] 轉換為4位元組
idivl S R[%edx] = R[%edx]:R[%eax] % S;R[%eax] = R[%edx]:R[%eax] / S; 有符號除法,儲存餘數和商
divl S R[%edx] = R[%edx]:R[%eax] % S;R[%eax] = R[%edx]:R[%eax] / S; 無符號除法,儲存餘數和商

注:64位數通常儲存為,高32位放在edx,低32位放在eax。

條件碼:

條件碼暫存器描述了最近的算數或邏輯操作的屬性。

CF:進位標誌,最高位產生了進位,可用於檢查無符號數溢位。

OF:溢位標誌,二進位制補碼溢位——正溢位或負溢位。

ZF:零標誌,結果為0。

SF:符號標誌,操作結果為負。

比較指令:

指令 基於 描述
cmpb S2,S1 S1 – S2 比較位元組,差關係
testb S2,S1 S1 & S2 測試位元組,與關係
cmpw S2,S1 S1 – S2 比較字,差關係
testw S2,S1 S1 & S2 測試字,與關係
cmpl S2,S1 S1 – S2 比較雙字,差關係
testl S2,S1 S1 & S2 測試雙字,與關係

訪問條件碼指令:

指令 同義名 效果 設定條件
sete D setz D = ZF 相等/零
setne D setnz D = ~ZF 不等/非零
sets D D = SF 負數
setns D D = ~SF 非負數
setg D setnle D = ~(SF ^OF) & ZF 大於(有符號>)
setge D setnl D = ~(SF ^OF) 小於等於(有符號>=)
setl D setnge D = SF ^ OF 小於(有符號<)
setle D setng D = (SF ^ OF) | ZF 小於等於(有符號<=)
seta D setnbe D = ~CF & ~ZF 超過(無符號>)
setae D setnb D = ~CF 超過或等於(無符號>=)
setb D setnae D = CF 低於(無符號<)
setbe D setna D = CF | ZF 低於或等於(無符號<=)

跳轉指令:

指令 同義名 跳轉條件 描述
jmp Label 1 直接跳轉
jmp *Operand 1 間接跳轉
je Label jz ZF 等於/零
jne Label jnz ~ZF 不等/非零
js Label SF 負數
jnz Label ~SF 非負數
jg Label jnle ~(SF^OF) & ~ZF 大於(有符號>)
jge Label jnl ~(SF ^ OF) 大於等於(有符號>=)
jl Label jnge SF ^ OF 小於(有符號<)
jle Label jng (SF ^ OF) | ZF 小於等於(有符號<=)
ja Label jnbe ~CF & ~ZF 超過(無符號>)
jae Label jnb ~CF 超過或等於(無符號>=)
jb Label jnae CF 低於(無符號<)
jbe Label jna CF | ZF 低於或等於(無符號<=)

轉移控制指令:(函式呼叫):

指令 描述
call Label 過程呼叫,返回地址入棧,跳轉到呼叫過程起始處,返回地址是call後面那條指令的地址
call *Operand
leave 為返回準備好棧,為ret準備好棧,主要是彈出函式內的棧使用及%ebp

家庭作業參考答案

3.54

x at %ebp+8, y at %ebp+12, z at %ebp+16, return value at %eax

int decode2(int x,int y,int z){
    int temp1 = z-y;
    int temp2 = temp1<<15;
    temp2 = temp2>>15;
    return (x^temp1)*temp2;
}

3.55

ll_t is defined as long long

void store_prod(ll_t *dest, ll_t x, int y){
    *dest = x*y;
}

dest at %ebp+8, x at %ebp+12, y at %ebp+20

movl 12(%ebp),%esi  ;get x(long long)的低位
movl 20(%ebp),%eax  ;get y(int)
movl %eax,%edx      ;備份y
sarl $31,%edx       ;獲取y的符號位
movl %edx,%ecx      ;備份y的符號位
imull %esi,%ecx     ;x的低位無符號乘法乘以y的符號位(0或-1 即 全0或全1)
movl 16(%ebp),%ebx  ;get x(long long)的高位
imull %eax,%ebx     ;x的高位乘以y
addl %ebx,%ecx      ;%ecx=x高位*y+x低位*y的符號位(補全下面無符號的缺少部分)
mull %esi           ;%edx=(x的低位*y)的高位 %eax=(x的低位*y)的低位(這一步無符號)
leal (%ecx,%edx),%edx;%edx = %ecx+%edx
movl 8(%ebp),%ecx   ;get dest
movl %eax,(%ecx)    ;dest[0] = (x的低位*y)的低位
movl %edx,4(%ecx)   ;dest[1] = (x的低位*y)的高位+x的高位*y+x的低位*y的符號位(0或者-1)
32位機器模擬64位機器的有符號乘法
x表示 long long x 的值
x = xh*2^32 + xl
x*y = y*xh*2^32 + y*xl 完整形式是96位長 但僅需要64位
因此我們需要y*xl的完整64位和y*xh的低32位 並分開儲存

3.56

一個知識點:位移操作可以是一個立即數或者是存放在單位元組暫存器元素%cl中

A.
x:%esi,n:%ebx,result:%edi,mask:%edx
B.
result=0x55555555; //‭0101 0101 0101 0101 0101 0101 0101 0101‬
mask=0x80000000;
C.
mask!=0
D.
mask每次迴圈右移n位
E.
result每次迴圈和x&mask的值進行異或
int loop(int x,int n){
    int result = 0x55555555;
    int mask;
    for(mask = 0x80000000;mask!=0;mask=(unsigned)mask>>n){//需要注意邏輯右移 將有符號數轉化為無符號
        result ^= mask&x;
    }
    return result;
}

3.57

int cread_alt(int *xp){
    int temp = 0;
    int *p = xp?xp:&temp;
    return *p;
}

3.58

typedef enum {MODE_A, MODE_B, MODE_C, MODE_D, MODE_E} mode_t;
int switch3(int *p1, int *p2, mode_t action){
    int result = 0;
    switch(action){
        case MODE_A:
            result = *p1;
            *p1 = *p2;
            break;
        case MODE_B:
            result = *p1+*p2;
            *p2 = result;
            break;
        case MODE_C:
            *p2 = 15;
            result = *p1;
        case MODE_D:
            *p2 = *p1;
        case MODE_E:
            result = 17;
            break;
        default:
            result = -1;
            break;
    }
    return result;
}

選一個部分的彙編程式碼註釋解釋一下

.L14(MODE_B):
movl 12(%ebp),%edx    ;get p2 reuslt = p2
movl (%edx),%eax      ;temp_p2 = p2
movl %eax,%edx        ;result = *p2
movl 8(%ebp),%ecx     ;get p1 temp_p1 = p1
addl (%ecx),%edx      ;result += *p1此時result = *p1+*p2
movl 12(%ebp),%eax    ;get p2 
movl %edx,(%eax)      ;*p2 = result
jmp .L19              ;

3.59

int swith_prob(int x, int n){
    int result = x;
    switch(n){
        case 40:
        case 42:
            result <<= 3;
            break;
        case 43:
            result >>= 3;
            break;
        case 44:
            result <<= 3;
            result -= x;
        case 45:
            result *= result;
        case 41:
        default:
            retult += 17;
            break;
    }
    return result;
}

解釋和部分彙編程式碼註釋

從gdb打印出來的內容可以看出40的情況和42是一樣的因此40內容為空,其後緊跟42
44跳轉至後緊接著執行了45跳轉的內容 45之後也無跳轉 因此44和45沒有break
41的情況與default相同因此41置為空寫在defaul前
mov 0xc(%ebp),%eax  ;%ebp+12的位置獲取第二個引數,即n
sub 0x28,%eax       ;n-40(0x28為16進位制 轉為10進製為2*16+8=40)
cmp 0x5,%eax        ;n-40 與 5
ja 8048435(switch_pro+0x15);n-40-5>0? 即n若大於45直接返回

3.60

A.從彙編程式碼看出
A[i][j][k] = A+(63i+9j+k)*4
B.
解決方程T=9, S*T=63, R*S*T*4=2772
T = 9
S = 7 
R = 11

3.61

int var_prod_ele(int n, int A[n][n], int B[n][n], int i,int k){
    int result = 0;
    int *a_l = &A[i][0];
    int *a_r = &A[i][n];
    int *b_l = &b[0][k];
    while(a_l!=a_r){
        result += (*a_l)*(*b_l);
        b_l += n;
        a_l++;
    }
    return result;
}

可自行檢視彙編程式碼驗證

L3:
movl (%ebx), %ecx
imull (%edx), %ecx
addl %ecx, %eax
addl %edi, %ebx
addl $4, %edx
cmpl %edx, %esi
jne L3

3.62

A.
M = 76/4 = 19
B.
%edi 儲存 i
%ecx 儲存 j

使用指標進行優化

void transpose(int A[M][M]){
    int i,j;
    for(i=0;i<M;i++){
        int *temp1 = &A[i][0];
        int *temp2 = &A[0][i];
        for(j=0;j<i;j++){
            int t = *temp1;
            *temp1 = *temp2;
            *temp2 = t;
            temp1++;
            temp2 += M;
        }
    }
}

3.63

#define E1(n) 3*n
#define E2(n) 2*n-1

3.64

A.
8(%ebp)  result
12(%ebp) s1.p
16(%ebp) s1.v

B.
------------%ebp
s2.sum
s2.prod
s1.v
s1.p
&s2
------------%esp

C.
將結構體變數的各個成員的值傳入函式

D.
將返回變數的地址傳遞出去

3.65

A=3,B=7

3.66

這個題看了好久,不得不佩服GCC

寫下詳細註釋

push %ebp    
mov  %esp,%ebp
push %ebx
mov  0x8(%ebp),%eax  ;get i
mov  0xc(%ebp),%ecx  ;get *bp
imul $0x1c,%eax,%ebx ;28*i
lea  0x0(,%eax,8),%edx;8*i
sub  %eax,%edx        ;7*i
add  0x4(%ecx,%ebx,1),%edx ;7i+(bp+28i+4)注意bp+4是a_struct a的首地址 +28i即是bp->a[i]即一個a_struct大小是28 同時 有*ap = (bp+28i+4)
mov  0xc8(%ecx),%eax  ;bp->right = bp+200
add  (%ecx),%eax      ;bp->left + bp->right 即 28*CNT = 200 - 4 即 CNT = 7
mov  %eax,0x8(%ecx,%edx,4);bp+8+4*(7i+(bp+28i+4))=(bp+28*i+4+4)(即ap->idx+4,apx->x[0])+*(bp+0x1c*i+4)*4
pop  %ebx
pop  %ebp
ret
A.
CNT = 7 
B.
a_struct{
    int idx;
    int x[6];
}

其實分析出大小就能做這個題 並不需要完全看懂彙編程式碼

3.67

A.
e1.p:0
e1.x:4
e2.y:0
e2.next:4
B.8
C.
void proc(union ele *up)
{
    up->e2.next->e1.x=*(up->e2.next->e1.p) - up->e2.y;
}

3.68

void good_echo()
{
    char c;
    int x = 0;
    while( x=getchar(), x!='\n' && x!=EOF)
    {
        putchar(x);
    }
}

3.69

long trace(tree_ptr tp){
    long result = 0;
    while(tp){
        result = tp->val;
        tp = tp->left;
    }
    return result;
}

輸出二叉樹最左邊節點的值

3.70

long traverse(tree_ptr tp){
    if(!tp) return 9223372036854775807;
    long v = tp->val;
    long left = traverse(tp->left);
    long result = traverse(tp->right);
    if(left <= result) result = left;
    if(v <= result) result = v;
    return result;
}

或者換一種寫法

long traverse(tree_ptr tp){
    long result =  9223372036854775807;
    if(tp){
        long lv = traverse(tp->left);
        long rv = traverse(tp->right);
        result = lv <= rv ? lv : rv;
        result = result > tp->val ? tp->val : result;
    }
    return result;
}

求二叉樹節點最小值

指令 效果
cmovle s,r 小於或等於 s->r
cmovg s,r 大於 s->r

詳見資料傳送指令

參考文獻

《深入理解計算機系統(第二版)》

https://blog.csdn.net/summerhust/article/details/7404340

https://blog.csdn.net/maidou0921/article/details/53907971