深刻理解計算機系統(第三版)做業題答案(第三章)

3.58

屏幕快照 2018-02-26 上午9.43.34.png

/*
 * x in %rdi, y in %rsi, z in %rdx
 * subq %rdx, %rsi      // y - z ==> y
 * imulq %rsi, %rdi     // x * y ==> x
 * movq %rsi, %rax      // y ==> %rax
 * salq $63, %rax       // << 63
 * sarq $63, %rax       // >> 63
 * xorq %rdi, %rax      // 這個時候的%rdi已是x*y ^ %rax
 * 所以能夠得出結論 (x*y) ^ ((y-z) << 63 >> 63)
 */
long decode2(long x, long y, long z) {
    return (x * y) ^ ((y - z) << 63 >> 63);
}

3.59

屏幕快照 2018-02-26 上午9.45.28.png

/*

    根據提示:
    x = 2^64 * x_h + x_l (x_h表示x的高64位,x_l表示x的低64位)
    y = 2^64 * y_h + y_l (y_h表示y的高64位,y_l表示x的低64位)

    x * y = (2^64 * x_h + x_l) * (2^64 * y_h + y_l)
          = 2^64 * x_h * 2^64 * y_h + 2^64 * x_h * y_l + x_l * 2^64 * y_h + x_l * y_l

    在上邊這個表達式中2^64 * x_h * 2^64 * y_h明顯已經越界,所以捨去,
    x * y = 2^64(x_h * y_l + x_l * y_h) + (x_l * y_l)

    上邊的公式很重要,它表達的就是x*y的乘積的樣式,根據p = 2^64 *p_h + p_l 再結合上邊的公式
    咱們得出的結論是:
    2^64(x_h * y_l + x_l * y_h) + (x_l * y_l) = 2^64 *p_h + p_l
    那麼2^64 *p_h = 2^64(x_h * y_l + x_l * y_h) + (x_l * y_l) - p_l
    p_h = (x_h * y_l + x_l * y_h) + (x_l * y_l)/2^64 - p_l/2^64

    (x_l * y_l)/2^64 表示相乘後右移64位正好是他們相乘後的高64位的值
    p_l/2^64 則爲0

    所以咱們就把任務簡化了,咱們接下來看彙編

    dest in %rdi, x in %rsi, y in %rdx

    stroe_prod:
    movq %rdx, %rax         // %rax = y, 此時y_l = %rax
    cqto                    // 該命令的做用是把%rax中的符號位擴展到%rdx中,此時y_h = %rdx
    movq %rsi, %rcx         // 這行命令的做用是配合下一行獲取x高64位的值
    sarq $63, %rcx          // 獲取x的高64的值x_h = %rcx
    imulq %rax, %rcx        // 計算y_l * x_h = %rax * %rcx
    imulq %rsi, %rdx        // 計算y_h * x_l = %rdx * %rsi
    addq %rdx, %rcx         // 計算x_h * y_l + x_l * y_h的值
    mulq %rsi               // 該命令是計算%rax * %rsi的值,也就是x_l * y_l的值
    addq %rcx, %rdx         // 根據上邊咱們得出的結論,進行相加處理

*/

3.60

屏幕快照 2018-02-26 上午9.47.09.png

/*
    咱們先寫出彙編的註釋:
    x in %rdi, n in %esi
    loop:
    movl %esi, %ecx         // %ecx = n
    movl $1, %edx           // %edx = 1
    movl $0, %eax           // %eax = 0
    jmp .L2                 // 跳轉到L2
    .L3:
    movq %rdi, %r8          // %r8 = x
    andq %rdx, %r8          // %r8 &= %rdx
    orq %r8, %rax           // %rax |= %r8
    salq %c1, %rdx          // %rdx <<= %cl
    .L2:
    testq %rdx, %rdx        // %rdx & %rdx
    jne .L3                 // if != jump to .L3

    根據.L2咱們能夠得出的結論是若是%rdx的值爲0 就繼續循環

    .L3中作了什麼事呢?
    咱們知道%rdx的初始值爲1,返回值%rax的值爲0,那麼.L3中的解釋爲:
    1. x &= %rdx
    2. %rax |= x
    3. %rdx << n的低8位的值,也是爲了保護位移


    經過分析,咱們就能夠得出結論,該函數的目的是得出x中n的倍數的位掩碼
    答案:
    A:
    x --> %rdi
    n --> %esi
    result --> %rax
    mask --> %rdx

    B:
    result = 0
    mask = 1

    C:
    mask != 0

    D:
    mask <<= n

    E:
    result |= (x & mask)

    F:
    以下函數

*/

long loop(long x, int n) {
    long result = 0;
    long mask;
    for (mask = 1; mask != 0; mask = mask << n) {
        result |= (x & mask);
    }
    return result;
}

3.61

屏幕快照 2018-02-26 上午9.48.01.png

long cread(long *xp) {
    return (xp ? *xp : 0);
}

long cread_alt(long *xp) {
    return (!xp ? 0 : *xp);
}

3.62

屏幕快照 2018-02-26 上午9.48.53.png

屏幕快照 2018-02-26 上午9.49.05.png

typedef enum {MODE_A, MODE_B, MODE_C, MODE_D, MODE_E} mode_t;

long switch3(long *p1, long *p2, mode_t action) {
    long result = 0;
    switch(action) {
        case MODE_A:
            result = *p2;
            *p2 = *p1;
            break;
        case MODE_B:
            result = *p1 + *p2;
            *p1 = result;
            break;
        case MODE_C:
            *p1 = 59;
            result = *p2;
            break;
        case MODE_D:
            result = *p2;
            *p1 = result;
            result = 27;
            break;
        case MODE_E:
            result = 27;
            break;
        default:
            result = 12;
    }

    return result;
}

3.63

屏幕快照 2018-02-26 上午9.50.09.png

屏幕快照 2018-02-26 上午9.50.17.png

/*
    sub $0x3c, %rsi     // %rsi = n - 60
    cmp $0x5, %rsi      // 比較%rsi : 5
    ja 4005c3           // 大於就跳轉
    jmpq *0x4006f8(,%rsi,8)     // 這一行的目的是直接在跳轉表中獲取地址而後跳轉

    // 所以下邊這些彙編代碼就是對應跳轉表中的地址

    4005a1對應的index爲0和2:
    lea 0x0(,%rdi,8), %rax      // result = 8x

    4005c3對應的index爲1,也就是case 1,經過觀察,它用的就是default的指令
    因此case 1 在switch中是缺失的

    4005aa對應的index爲3:
    mov %rdi,%rax       // result = x
    sar $0x3,%rax       // result >>= 3
    也就是result = x / 8

    4005b2對應的index爲4:
    mov %rdi,%rax       // result = x
    shl $0x4,%rax       // result <<= 4
    sub %rdi,%rax       // result -= x
    mov %rax,%rdi       // x = result
    也就是result = x * 15; x = result

    4005bf對應的index爲5:
    imul %rdi,%rdi      // x *= x

    lea 0x4b(%rdi), %rax    // result = 75 + x

   通過上邊的分析,就很容易得出結論了,可是別忘了要把index加上60

*/

long switch_prob(long x, long n) {
    long result = x;
    switch(n) {
        case 60:
        case 62:
            result = 8 * x;
            break;
        case 63:
            result = x / 8;
            break;
        case 64:
            result = 15 * x;
            x = result;
        case 65:
            x *= x;
        default:
            result = 75 + x;
    }
    return result;
}

3.64

屏幕快照 2018-02-26 上午9.51.10.png

設L爲數組元素的大小,X_a表示數據的起始地址
&A[i][j][k] = X_a + L(i * S * T + j * T + k)

咱們再進一步分析彙編代碼:
i in %rdi, j in %rsi, k in %rdx, dest in %rcx

leaq (%rsi,%rsi,2), %rax        // %rax = 3j
leaq (%rsi,%rax,4), %rax        // %rax = 13j
movq %rdi, %rsi                 // %rsi = i
salq $6, %rsi                   // 結合上一條指令,%rsi = i << 6
addq %rsi, %rdi                 // %rdi = 65i
addq %rax, %rdi                 // %rdi = 65i + 13j
addq %rdi, %rdx                 // %rdx = 65i + 13j + k
movq A(,%rdx,8), %rax           // %rax = *(A + 8(65i + 13j + k))
movq %rax, (%rcx)               // *dest = *(A + 8(65i + 13j + k))
movl $3640, %eax                // %rax = 3640


使用A + 8(65i + 13j + k)和最上邊的公式對比後發現:
L: 8
T: 13
S: 5

要求出R還必須用到3640這個值
R * T * S * L = 3640
R = 3640 / 8 / 13 / 5 = 7

R: 7

3.65

屏幕快照 2018-02-26 上午9.52.10.png

咱們先假設M爲4,咱們假設矩陣A爲:

1  2   3  4
5  6   7  8
9  10  11 12
13 14  15 16

那麼在用函數transpose處理以後,矩陣變成了
1  5  9  13
2  6  10 14
3  7  11 15
4  8  12 16

能夠看出對矩陣沿着對角線進行了轉換。咱們繼續看彙編代碼
下邊的彙編代碼只是函數中內循環中的代碼

.L6:
movq (%rdx), %rcx       // %rcx = A[i][j]
movq (%rax), %rsi       // %rsi = A[j][i]
movq %rsi, (%rdx)       // A[i][j] = A[j][i]
movq %rcx, (%rax)       // A[j][i] = A[i][j]
addq $8, %rdx           // %rdx += 8
addq $120, %rax         // %rax += 120
cmpq %rdi, %rax         //
jne .L6                 //

咱們很容易就發現,指向A[i][j]的寄存器爲%rdx,指向A[j][i]的寄存器爲%rax

求M最關鍵的是找出%rax寄存器移動的規律,由於%rdx也就是A[i][j] + 8 就表示右移一位
而%rax則要移動M * 8位
所以M = 120 / 8 = 15

上邊的寄存器%rdi應該放的就是i == j時的A[i][j]的地址

3.66

屏幕快照 2018-02-26 上午9.52.53.png

首先咱們寫出彙編代碼的註釋:

n in %rdi, A in %rsi, j in %rdx

sum_col:
    leaq 1(,%rdi,4), %r8        // %r8 = 1 + 4n
    leaq (%rdi,%rdi,2), %rax    // %rax = 3n
    movq %rax, %rdi             // %rdi = 3n
    testq %rax, %rax            // 3n & 3n
    jle .L4                     // if <= 0 .L4
    salq $3, %r8                // %r8 = (1 + 4n) << 3
    leaq (%rsi,%rdx,8), %rcx    // %rcx = 8j + A
    movl $0, %eax               // %rax = 0
    movl $0, %edx               // %rdx = 0
   .L3:
    addq (%rcx), %rax           // %rax += *%rcx
    addq $1, %rdx               // %rdx += 1
    addq %r8, %rcx              // %rcx += (1 + 4n) << 3
    cmpq %rdi, %rdx             // %rdx : 3n
    jne .L3
    rep; ret
   .L4:
    movl $0, %eax               // %rax = 0
    ret


很明顯,.L3上邊的代碼都是爲循環準備數據的

若是n = 0 那麼就直接返回result = 0

而後初始化局部變量%rdx保存i的值,%rax保存result的值,%rcx保存每一行j的地址,
而後進入循環體.L3

由%rdx : 3n能夠看出打破循環的條件是 i == 3n 推導出:NR(n) = 3n

由%rcx += (1 + 4n) << 3能夠看出,%rcx每次都移動了一行的寬度,也就是NC(n) = (1 + 4n) << 3

答案是:
NR(n) = 3n
NC(n) = (1 + 4n) << 3

3.67

屏幕快照 2018-02-26 上午9.53.41.png

屏幕快照 2018-02-26 上午9.53.55.png

屏幕快照 2018-02-26 上午9.54.04.png

屏幕快照 2018-02-26 上午9.54.12.png

咱們先給彙編代碼添加註釋:

x in %rdi, y in %rsi, z in %rdx
eval:
    subq $104, %rsp     // 給棧分配了104個字節的空間
    movq %rdx, 24(%rsp) // 把z的值保存在偏移量爲24的位置
    leaq 24(%rsp), %rax // %rax保存了z的指針
    movq %rdi, (%rsp)   // 把x的值保存在偏移量爲0的位置
    movq %rsi, 8(%rsp)  // 把y的值保存在偏移量爲8的位置
    movq %rax, 16(%rsp) // 把z的指針值保存在偏移量爲16的位置
    leaq 64(%rsp), %rdi // 把偏移量爲64的指針賦值給%rdi,當作參數傳遞給後邊的函數
    call process

    movq 72(%rsp), %rax // 取出偏移量爲72的值賦值給%rax
    addq 64(%rsp), %rax // +
    addq 80(%rsp), %rax // +
    addq $104, %rsp      // 恢復棧頂指針
    ret


process:
    movq %rdi, %rax         // 把參數保存到%rax
    movq 24(%rsp), %rdx     // %rdx = &z 這裏有點意思,當調用call後會把函數下邊代碼的地址壓入棧中
    movq (%rdx), %rdx       // %rdx = z
    movq 16(%rsp), %rcx     // %rcx = y
    movq %rcx, (%rdi)       // 把y保存到偏移量爲64 + 8 = 72的位置
    movq 8(%rsp), %rcx      // %rcx = x
    movq %rcx, 8(%rdi)      // 把x保存到偏移量爲64 + 8 + 8 = 80的位置
    movq %rdx, 16(%rdi)     // 把z保存到偏移量爲64 + 8 + 16 = 88的位置
    ret


經過上邊的註釋,下邊的問題就很清楚了

A:
-----------     <-- 108

    z
-----------     <-- 24
    &z
-----------     <-- 16
    y
-----------     <-- 8
    x
-----------     <-- %rsp

B:
傳遞了一個相對於%rsp偏移量爲64的指針

C:
直接使用偏移量來訪問的s的元素

D:
直接設置偏移量

E:
-----------     <-- 108


-----------     <-- 88
    z
-----------     <-- 80
    x
-----------     <-- 72
    y
-----------     <-- 64 --- %rax

-----------     <-- 32
    z
-----------     <-- 24
    &z
-----------     <-- 16
    y
-----------     <-- 8
    x
-----------     <-- %rsp

F:
經過這個例子,咱們可以發現,若是把結構做爲參數,那麼實際傳遞的會是一個空的位置指針,函數把數據
存儲在這個位置上,同時返回值也是這個指針。

3.68

屏幕快照 2018-02-26 上午9.54.57.png

p in %rdi, q in %rsi
setVal:
    movslq 8(%rsi), %rax        // %rax = *(8 + q)
    addq 32(%rsi), %rax         // %rax += *(32 + q)

    movq %rax, 184(%rdi)        //


這個問題算是很是簡單的,由最後一條代碼再加上str的結構,咱們能夠得出這樣一個等式
4 * A * B + space = 184 因爲對齊原則是保證8的倍數,分別假設space爲7和0
==> 44 < A * B <= 46

%rax = *(8 + q) 能夠推斷出char array[B] 應該總共使用8個字節
由於須要考慮對齊原則,因此先得出 B <= 8

short s[A] %rax += *(32 + q)
咱們t佔用4個字節  ==> 4 + A * 2 <= 32 - 8 <= 24

因而咱們有三個公式來作判斷:
44 < A * B <= 46
B <= 8
A <= 10

那麼A * B 的值只能是45 組合就是 5 * 9
因爲 B <= 8 所以 B = 5 A = 9

咱們再驗證一番,short s[A] 因爲對齊原則 佔用了20個字節,跟彙編代碼一致

答案:
A = 9
B = 5

3.69

屏幕快照 2018-02-26 上午9.55.52.png

i in %rdi, bp in %rsi
test:
    mov 0x120(%rsi), %ecx           // %rcx = *(288 + bp)
    add (%rsi), %ecx                // %rcx = *(288 + bp) + *bp
    lea (%rdi,%rdi,4), %rax         // %rax = 5 * i
    lea (%rsi,%rax,8), %rax         // %rax = 5 * i * 8 + bp
    mov 0x8(%rax), %rdx             // %rdx = *((5 * i * 8 + bp) + 8)
    movslq %ecx, %rcx
    mov %rcx, 0x10(%rax,%rdx,8)     // &(16 + %rax + 8 * %rdx) = %rcx
    retq


由 %rdx = (5 * i * 8 + bp) + 8 能夠推導出 a_struct a[CNT] 每一個元素佔40個字節,first佔8個字節
==>
CNT = (288 - 8) / 40 ==> CNT = 7

本題重點理解%rax 和 %rdx中保存的是什麼的值,
%rax中保存的是ap的值,而%rdx中保存的是ap->idx的值,理解了這一層接下來就簡單了

說明ap->idx保存的是8字節的值,根據 &(16 + %rax + 8 * %rdx) = %rcx 能夠得出idx應該是結構體的第一個變量long idx

若是結構體佔用了40個字節 , 那麼數組x應該佔用 40 - 8 也就是32個字節,每一個元素佔8個,能夠容納4個元素

typedef struct {
    long idx;
    long x[4];
}a_struct;


這個題目最重要的地方是理解mov 0x8(%rax), %rdx 這段代碼,它是求ap->idx的值。

3.70

屏幕快照 2018-02-26 上午9.56.35.png

A:
0
8
0
8

B:
e1最多須要16個字節
e2最多須要16個字節
所以 總共須要16個字節

C:
up in %rdi
proc:
    movq 8(%rdi), %rax      // %rax = *(8 + up) 取出偏移量爲8的地址
    movq (%rax), %rdx       // %rdx = *%rax 取出該地址中的值
    movq (%rdx), %rdx       // 取出指針指向的值
    subq 8(%rax), %rdx      // 用該值減去 *(%rax + 8)
    movq %rdx, (%rdi)       //
    ret

通常來講 若是一個寄存器,好比說%rax 在下邊的使用中用到了(%rax),咱們就認定該寄存器保存的值爲指針

movq 8(%rdi), %rax  %rax保存了up偏移量爲8的指針值,在該函數中偏移量爲8仍是指針的只能是e2的next
==> %rax = up -> e2.next

movq (%rax), %rdx %rdx 一樣保存的是指針,對(%rax)取值獲得的是up下一個unio的指針
==> %rdx = *(up -> e2.next)

movq (%rdx), %rdx  這行代碼事後,%rdx就再也不是指針了,是一個值,但運行以前,%rdx是個指針
==> %rdx = *(*(up -> e2.next) -> e2.p)

subq 8(%rax), %rdx  咱們知道%rax是個指針 指向next +8後
==> 8(%rax) = *(up -> e2.next) -> e1.y

答案:
up -> e2.x = *(*(up -> e2.next) -> e2.p) - *(up -> e2.next) -> e1.y;

3.71

屏幕快照 2018-02-26 上午9.57.15.png

#include <stdio.h>
#include <assert.h>
#define BUF_SIZE 12

void good_echo(void) {
  char buf[BUF_SIZE];
  while(1) {
    /* function fgets is interesting */
    char* p = fgets(buf, BUF_SIZE, stdin);
    if (p == NULL) {
      break;
    }
    printf("%s", p);
  }
  return;
}

int main(int argc, char* argv[]) {
  good_echo();
  return 0;
}

3.72

屏幕快照 2018-02-26 上午9.57.58.png

屏幕快照 2018-02-26 上午9.58.06.png

咱們先畫一畫棧圖:

----------


----------      <-- %rbp  0


---------- s1     <-- -16
    e1
----------
    p
---------- p
    e2
---------- s2


A:
s2 = %rsp - 16 - (-16 & (8n + 30)) 因爲s2 = %rsp - 16 因此
s2 = s1 - (-16 & (8n + 30))

這裏的-16的十六進制表示爲0xfffffff0,之因此用& 就是爲了求16的整數倍

B:
p = (s2 + 15) & 0xfffffff0

C:
s2 = s1 - (0xfffffff0 & (8n + 30)) 根據這個公式
當n是偶數的時候,咱們能夠把式子簡化爲 s2 = s1 - (8 * n + 16)
當n是奇數的時候,咱們能夠把式子簡化爲 s2 = s1 - (8 * n + 24)

先求e1最小的狀況
e1和e2是對立的關係,要想e1最小,那麼e2就要最大,e2最大也就是15,
n是偶數的時候,e1 = 16 - 15 = 1 這個時候s1 % 16 == 1

e1最大的狀況:
e2 == 0 時 e1最大, 當n是奇數的時候,e1 == 24 這個時候s1 % 16 == 0(p中多處了一個8字節)

D:
s2 確保可以容納足夠的p, p可以保證自身16對齊

3.73

屏幕快照 2018-02-26 上午9.58.50.png

#include <stdio.h>
#include <assert.h>

typedef enum {NEG, ZERO, POS, OTHER} range_t;

range_t find_range(float x) {
  __asm__(
      "vxorps %xmm1, %xmm1, %xmm1\n\t"
      "vucomiss %xmm1, %xmm0\n\t"
      "jp .P\n\t"
      "ja .A\n\t"
      "jb .B\n\t"
      "je .E\n\t"
      ".A:\n\t"
      "movl $2, %eax\n\t"
      "jmp .Done\n\t"
      ".B:\n\t"
      "movl $0, %eax\n\t"
      "jmp .Done\n\t"
      ".E:\n\t"
      "movl $1, %eax\n\t"
      "jmp .Done\n\t"
      ".P:\n\t"
      "movl $3, %eax\n\t"
      ".Done:\n\t"
      );
}

int main(int argc, char* argv[]) {
  range_t n = NEG, z = ZERO, p = POS, o = OTHER;
  assert(o == find_range(0.0/0.0));
  assert(n == find_range(-2.3));
  assert(z == find_range(0.0));
  assert(p == find_range(3.33));
  return 0;
}

3.74

屏幕快照 2018-02-26 上午9.59.29.png

#include <stdio.h>
#include <assert.h>

typedef enum {NEG, ZERO, POS, OTHER} range_t;

range_t find_range(float x) {
  __asm__(
      "vxorps %xmm1, %xmm1, %xmm1\n\t"
      "movq $1, %rax\n\t"
      "movq $2, %r8\n\t"
      "movq $0, %r9\n\t"
      "movq $3, %r10\n\t"
      "vucomiss %xmm1, %xmm0\n\t"
      "cmovg %r8, %rax\n\t"
      "cmove %r9, %rax\n\t"
      "cmovpq %r10, %rax\n\t"
      );
}

int main(int argc, char* argv[]) {
  range_t n = NEG, z = ZERO, p = POS, o = OTHER;
  assert(o == find_range(0.0/0.0));
  assert(n == find_range(-2.3));
  assert(z == find_range(0.0));
  assert(p == find_range(3.33));
  return 0;
}

3.75

屏幕快照 2018-02-26 上午10.00.10.png

屏幕快照 2018-02-26 上午10.00.19.png

這個題考察的是複數的概念

複數 = 實數 + 虛數

傳參的時候,有這樣的規律

(複數1, 複數2, 複數3...) 對應的浮點寄存器就會是:

%xmm0, %xmm1, %xmm2, %x

總結

看本章的過程中,彷彿回到了大學時光,在讀的的過程當中,書本上的練習題作的還能夠,可是感受不少前邊講過的東西仍是不太清楚,因而在讀完後又從新讀了一遍,在閱讀第二遍的過程當中, 注意到了不少細節,好比以前push 和 pop 有點迷惑,如今就很是清晰了html

要想記住書本中的內容,看來仍是要多讀幾遍。我感受在該章中學到最多的是理解了c語言在機器代碼級別的表示,對數據在內存中的操做更加了解了,不得不感慨編譯器的強大,如今還感受不出這些東西在實際工做中的用處,但對運行時棧的理解仍是頗有用的。git

我已經把答案上傳到了個人github中深刻理解計算機系統(第三版)做業題答案(第三章)github

在答題的過程當中,我參考了這兩個網站1 2數組

有錯誤的地方能夠直接指出,歡迎討論。app

相關文章
相關標籤/搜索