1. 程式人生 > >[第三章] 深入理解計算機系統第三版 家庭作業參考答案

[第三章] 深入理解計算機系統第三版 家庭作業參考答案

人非聖賢孰能無過,歡迎大家提問與糾錯

3.58

long decode2(long x, long y, long z) {
	y -= z;
	x *= y;
	return ((y << 63) >> 63) ^ x;
}

3.59

∵ x = 2^64 * xh + xl; y = 2^64 * yh + yl;
∴ x * y = 2^128 * xh * yh + 2^64 * xh * yl + 2^64 * yh * xl + xl * yl
因為是128位,所以(x * y) mod 2^128 才是正解
∴ x * y = 2^64 * xh * yl + 2^64 * yh * xl + xl * yl

rdi = dest; rsi = xl; rdx = yl;
cqto 是將 rax 符號擴充套件到 rdx : rax,即 yh : yl;而 xh : xl 是用 rcx : rsi 來表示的;

store_prod:
    movq   %rdx, %rax   # rax = yl
    cqto                # 符號擴充套件 rdx : rax = yh : yl
    movq   %rsi, %rcx   # rcx = xl
    sarq   $63,  %rcx   # 符號擴充套件 rcx : rsi = xh : yl;與cqto功能相同
    imulq  %rax, %rcx   # rcx = xh * yl
    imulq  %rsi, %rdx   # rdx = yh * xl
    addq   %rdx, %rcx   # rcx = xh * yl + yh * xl
    mulq   %rsi         # rdx : rax = xl * yl;
                        # mul是無符號運算,因為此時 x 和 y 都是 128 位的值,符號在高位上,低位運算不應使用有符號運算;
                        # 注意這裡 xl * yl 可能產生向高位的進位
    addq   %rcx, %rdx   # rdx = rdx + xh * yl + yh * xl
                        # 因為可能有低位進位,所以不直接使用 rcx 作為結果的高位,而是要加上 rdx
    movq   %rax, (%rdi) # 將 rax 的值放到結果的低位
    movq   %rdx, 8(%rdi)# 將 rdx 的值放到結果的高位
    ret

3.60

loop:
    movl  %esi, %ecx # ecx = esi
    movl  $1, %edx   # edx = 1 = mask
    movl  $0, %eax   # eax = 0 = result
    jmp   .L2
.L3:
    movq  %rdi, %r8  # r8 = x
    andq  %rdx, %r8  # r8 = r8 & rdx = x & mask
    orq   %r8, %rax  # rax = rax | r8 = result | (x & mask)
    salq  %cl, %rdx  # rdx = rdx << cl = mask << (n & 0xFF)
.L2:
    testq %rdx, %rdx
    jne   .L3        # if rdx != 0 -> jmp L3
    rep; ret

A. rdi = x; rsi = n; rax = reault; rdx = mask
B. result = 0; mask = 1;
C. mask != 0
D. mask << (n & 0xFF)
E. result | (x & mask)
F.

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

3.61

不要在判斷指標是否為空之前就解引用

long cread_alt(long *xp) {
	long zero = 0;
	return *(xp ? xp : (&zero));
}

3.62

很簡單,不要忘記 break

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:
    *p1 = *p2;
    result = 27;
    break;
case MODE_E:
    result = 27;
    break;
default:
    result = 12;

3.63

簡單的 switch 和跳轉表,只需注意 break 和 順延

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

3.64

A.
擴充套件到三維:
定義 T D[R][S][T];
&D[i][j][k] = xa + L(S * T * i + T * j + k)

B.
根據公式,知索引為 S * T * i + T * j + k;
則 R = 3640/5/13/8 = 7;S = 65/13 = 5;T = 13

3.65

根據 rdx 和 rax 每次迴圈的遞增值即可判斷
A. rdx
B. rax
C. M = 120/8 = 15

3.66

sum_col:
    leaq   1(, %rdi, 4), %r8        # r8 = 4 * n + 1
    leaq   (%rdi, %rdi, 2), %rax    # rax = 3 * n
    movq   %rax, %rdi               # rdi = rax = 3 * n
    testq  %rax, %rax
    jle    .L4                      # if rax == 0 -> jmp L4
    salq   $3, %r8                  # r8 = 8 * (4 * n + 1)
    leaq   (%rsi, %rdx, 8), %rcx    # rcx = A + 8 * j = A[0][j]
    movl   $0, %eax                 # result = 0
    movl   $0, %edx                 # i = 0
.L3:
    addq   (%rcx), %rax             # result = result + A[i][j]
    addq   $1, %rdx                 # i += 1
    addq   %r8, %rcx                # 每次加8 * (4n + 1),說明每一行有 4n + 1 個元素,因此 NC(n) 為 4n + 1
    cmpq   %rdi, %rdx               
    jne    .L3                      # rdx != 3*n 就繼續迴圈,因此 NR(n) 為 3 * n
    rep; ret
.L4:
    movl $0, %eax
    ret

巨集定義如下:

#define NR(n) (3 * (n))
#define NC(n) (4 * (n) + 1)

3.67

A.
表格中的地址指的是相對於 eval 的原始棧頂的地址,自上向下遞減的

相對地址 棧值 當前棧頂
-104 x <-----
-96 y
-88 &z
-80 z
垃圾值
0 eval 的原始棧頂

B.
傳遞了整個結構體 strA,包括 x y &z

C.
使用 rsp 加偏移訪問

相對地址 棧值 當前棧頂
-112 返回地址 <-----
-104 x
-96 y
-88 &z
-80 z
垃圾值
-40 u[0]
-32 u[1]
-24 q
垃圾值
0 eval 的原始棧頂

D.
使用 rdi 加偏移訪問

E.
同樣是使用 rsp 加偏移訪問

相對地址 棧值 當前棧頂
-104 x <-----
-96 y
-88 &z
-80 z
垃圾值
-40 u[0]
-32 u[1]
-24 q
垃圾值
0 eval 的原始棧頂

F.
由於結構體的複雜性,是通過儲存在棧上訪問的

3.68

做這個題時應注意 資料對齊
對齊原則:任何 K 位元組的基本物件的地址必須是 K 的倍數

setVal:
    movslq  8(%rsi), %rax   # 為使 int 四位元組對齊,可得 5 <= B <= 8
    addq    32(%rsi), %rax  # 為使 long 八位元組對齊,7 <= A <= 10
    movq    %rax, 184(%rdi) # 180 <= A * B * 4 <= 184
    ret

解得 A=9 B=5

3.69

<test>:
    mov    0x120(%rsi), %ecx        # ecx = *(bp + 288)
    add    (%rsi), %ecx             # ecx += *bp
                                    # 上兩行可推斷 288 是 last 與 first 的首地址之差
    lea    (%rdi, %rdi, 4), %rax    # rax = 5i
    lea    (%rsi, %rax, 8), %rax    # rax = bp + 40i
    mov    0x8(%rax), %rdx          # rdx = *(bp + 40i + 8)
    movslq %ecx, %rcx               # rcx = ecx(符號擴充套件)
                                    # ecx = n,將其符號擴充套件,賦值給 x 
                                    # 由此推斷 a_struct 中的 x 是長整型 long 的陣列
    mov    %rcx, 0x10(%rax, %rdx, 8)# 8 * (*(bp + 40i + 8)) + bp + 40i + 16 = rcx
    retq

比較簡單的推斷在上圖中列出;
難點在mov 0x8(%rax), %rdx # rdx = *(bp + 40i + 8)mov %rcx, 0x10(%rax, %rdx, 8)# 8 * (*(bp + 40i + 8)) + bp + 40i + 16 = rcx,bp + 8 + 40i 很容易猜出它是 b_struct 中 a[i] 的首地址,且 b_struct 八位元組對齊,結構 a_struct 的位元組數為 40;
而 8 * ( *(bp + 40i + 8) ) 可以知道這是索引,進而推斷 a_struct 中 idx 排在 x 前面;
那麼 bp + 40i + 16 就可以寫成 bp + 8 + 40i + 8,前一個 8 是 first 的偏移,後一個 8 是 idx 的偏移;

A.
根據推斷,結構 a_struct 的位元組數為 40
CNT = (288 - 8)/40 = 7
B.

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

3.70

A.

e1.p     0  
e1.y     8  
e2.x     0  
e2.next  8  

B.
16

C.

proc:
    movq    8(%rdi), %rax   # rax = *(up + 8)
    movq    (%rax), %rdx    # rdx = *(*(up + 8))
    movq    (%rdx), %rdx    # rdx = *(*(*(up + 8)))
    subq    8(%rax), %rdx   # rdx = *(*(*(up + 8))) - *(*(up + 8) + 8) 
    movq    %rdx, (%rdi)    # *up = rdx
    ret

根據上圖註釋推理出下圖程式碼:

void proc(union ele *up) {
	up->e2.x = *(up->e2.next->e1.p) - (up->e2.next->e1.y);
}

3.71

#include<stdio.h>
#define MAX 10
void good_echo() {
	char buffer[MAX];
	while (fgets(buffer, MAX, stdin) != NULL) {
		printf("%s", buffer);

		if (ferror(stdin)) {
			printf("\nError\n");
			return;
		}
	}
}

int main() {
	good_echo();
	system("pause");
}

3.72

A.
第5行 leaq 計算得到 8n + 30,然後 andq 將其向下舍入到最近接近的 16 的倍數;-16 = 0xFFFF…FFF0;

(8n + 30) & 0xFFFF…FFF0
= 8n + 30 - (8n + 30) mod 16

當 n 為偶數時:
上式 = 8n + 30 - (16 * n/2 + 30) mod 16
= 8n + 30 - 30 mod 16
= 8n + 16

當 n 為奇數時:
上式 = 8n + 30 - (16 * ((n+1)/2 - 1/2) + 30) mod 16
= 8n + 30 - ((-8 + 30)mod 16)
= 8n + 24

所以:
當 n 為偶數時:
s2 = s1 − (8n + 16)
當 n 為奇數時:
s2 = s1 − (8n + 24)

B.
p = (s2 + 15) & 0xFFFF…FFF0
利用了“偏置”將 s2 向上舍入到最近的 16 的倍數;

C.
這裡給出數學證明

已知:
p = s1 - 8n - e1                                       (1)

當 n 為偶數時:
p = (s2 + 15) & 0xFFFF…FFF0
= (s1 − (8n + 16) + 15) & 0xFFFF…FFF0
= (s1 - 8n - 1) & 0xFFFF…FFF0
= (s1 - 8n - 1) - (s1 - 8n - 1) mod 16
= s1 - 8n - 1 - (s1 - 1) mod 16                (2)

由 s2 = s1 − (8n + 16) 可得 e1 + e2 = 16
進而可設 0 <= e1 <= 16                          (3)

結合(1)(2)式可得:
s1 - 8n - 1 - (s1 - 1) mod 16 = s1 - 8n - e1
(-s1 + 1) mod 16 = -e1 + 1
-s1 + 1 = 16t - e1 + 1 (t 可為任意整數)
s1 = -16t + e1                                         (4)

結合(3)(4)式可得:
MAX_e1 = 16:s1 = 16t(t 可能為任意正整數)
MIN_e1 = 0:s1 = 16t(t 可能為任意正整數)

同理,當 n 為奇數時:
p = (s2 + 15) & 0xFFFF…FFF0
= s1 - 8n - 9 - (s1 - 1) mod 16

s1 - 8n - 9 - (s1 - 1) mod 16 = s1 - 8n - e1
(-s1 + 1) mod 16 = -e1 + 9
-s1 + 1 = 16t -e1 + 9
s1 = 16t + e1 - 8

0 <= e1 <= 24
MAX_e1 = 24:s1 = 16t(t 可能為任意正整數)
MIN_e1 = 0:s1 = 16t - 8(t 可能為任意正整數)

因此:
MIN_e1 = 0,n 為偶數,s1 為 16t ; n 為奇數,s1 為 16t -8;
MAX_e1 = 24,n 為奇數,s1 為 16t(t 可能為任意正整數)

D.
s2 將 8 * n 大小的空間偏移量保留為最小的 16 的倍數
p 以 16 位元組的倍數對齊

3.73

#include<stdio.h>

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

range_t find_range(float x) {
	int result;
	float zero = 0.0;
	asm(
		"vucomiss %[x], %[z]										# Compare 0:x		\n\t"
		"jp	.O														# NaN \n\t"
		"ja	.N														# 0 > x		\n\t"
		"jb	.P														# 0 < x		\n\t"
		"je	.Z														# 0 = x		\n\t"
		".O: mov $3, %[r]											 # NaN	\n\t"
		"jmp		.Done											# jmp done	\n\t"
		".P: mov $2, %[r]											 # POS \n\t"
		"jmp		.Done											# jmp done \n\t"
		".N: mov $0, %[r]									    	 # NEG \n\t"
		"jmp		.Done											# jmp done \n\t"
		".Z: mov $1, %[r]									    	 # ZERO \n\t"
		"jmp		.Done											# jmp done \n\t"
		".Done:nop"
		: [r] "=r" (result)											/*output*/
		: [x] "x" (x), [z] "x" (zero)								/*input*/
	);

	return result;
}

int main() {
	printf("%d %d %d %d\n", find_range(12.234), find_range(0.0), find_range(-11.433), find_range(0.0 / 0.0));
	return 0;
}

最開始我是想用 xmm1 的,卻總是有錯誤(見下圖),靈活變通一下,將 0.0 儲存在了一個區域性變數 zero 裡就行了。

findrange.c: In function ‘find_range’:
findrange.c:10:2: error: invalid 'asm': operand number missing after %-letter
  asm("vxorps %%xmm1, %%xmm1, %%xmm1  # Set %xmm1 = 0 \n\t"
  ^~~

3.74

#include<stdio.h>

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

range_t find_range(float x) {
	int result, temp;
	float zero = 0.0;
	asm(
		"mov $0, %%r8\n\t"
		"mov $2, %%r9\n\t"
		"mov $3, %%r10\n\t"
		"vucomiss %[x], %[z]										    # Compare 0:x		\n\t"
		"mov $1, %%rax													 # result = 0		\n\t"
		"cmovb %%r9, %%rax												# POS		\n\t"						
		"cmova %%r8, %%rax												# NEG		\n\t"
		"cmovp %%r10, %%rax												# NaN		\n\t"
		:
		: [x] "x" (x), [z] "x" (zero)									/*input*/
		: "%rax"
	);
}

int main() {
	printf("%d %d %d %d\n", find_range(12.234), find_range(0.0), find_range(-11.433), find_range(0.0 / 0.0));
	return 0;
}

3.75

A.
通過兩個連續的 xmm 暫存器,例如第一個複數引數以 %xmm0 和 %xmm1 傳遞,第二個以 %xmm2 和 %xmm3 傳遞,以此類推
B.
%xmm0 作為返回值的實部, %xmm1 作為返回值的虛部