深入理解計算機系統_3e 第三章家庭作業 CS:APP3e chapter 3 homework


#### 3.58
long decode2(long x, long y, long z)
{
  int result = x * (y - z);
  if((y - z) & 1)
    result = ~result;
  return result;
}

3.59

這個題考察的是2.3.42.3.5節的一個定理:w比特長度的兩個數相乘,會產生一個2w長度的數,不管這兩個數是無符號數還是補碼表示的有符號數,把結果截取的低w比特都是相同的。

所以我們可以用無符號數乘法指令mulq實現有符號數乘法:先把數有符號擴展致2w位,然后把這兩個2w位的數相乘,截取低2w位即可。

截取就是求模運算,即 mod 2^w。

store_prod
	movq	%rdx, %rax		#rax中保存y
	cqto					#將rax有符號擴展為rdx:rax,即rdx為全1
	movq	%rsi, %rcx		#rcx中保存x
	sarq	$63, %rcx		#rcx為為全1若x小於0,否則為0,即將x有符號擴展
	#下面把這兩個擴展的數當成無符號數進行運算,取低128bit。
	#此時y表示為rdx:rax,x表示為rcx:rsi, 即y = rdx*2^64 + rax, x = rcx*2^64 + rsi
	#x*y = rdx*rcx*2^128 + rdx*rsi*2^64 + rcx*rax*2^64 + rax*rsi
	#由於我們只需要取低128位,所以對x*y進行取模操作mod 128,得到公式:rdx*rsi*2^64mod2^128 + rcx*rax*2^64mod2^128 + rax*rsi
	#由於這里的寄存器都是64位的,所以對於rdx*rsi*2^64mod2^128這樣的操作我們可以直接使用imulq指令,截取兩個寄存器相乘的低64位,然后把他加到rax*rsi的高64位。
	#下面實現公式
	imulq	%rax, %rcx		#rcx*rax*2^64mod2^128(隨后放在高64位)
	imulq	%rsi, %rdx		#rdx*rsi*2^64mod2^128(隨后放在高64位)
	addq	%rdx, %rcx		#隨后放在高64位
	mulq	%rsi			#x*y即rax*rsi
	addq	%rcx, %rdx		#放在高64位
	movq	%rax, (%rdi)	#存儲低64位
	movq	%rdx, 8(%rdi)	#存儲高64位
	ret

3.60

A. x : %rdi n : %esi result : %rax mask : %rdx

B. result = 0 mask = 1

C. mask != 0

D. mask >>= n

E. result |= (x & mask)

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

3.61

long cread_alt(long *xp)
{
    static long tmp = 0;
  	if(xp == 0)
    {
        xp = &tmp;
    }
  	return *xp;
}

這個地方也是很無語,在我的環境下必須將tmp的存儲類型設置為靜態存儲,並且將gcc的優化設置為O3,這樣才能生成使用conditional transfer的指令(才能讓gcc相信優化是值得的。。):

00000000004004f0 <cread_alt>:
  4004f0:	48 85 ff             	test   %rdi,%rdi
  4004f3:	b8 38 10 60 00       	mov    $0x601038,%eax
  4004f8:	48 0f 44 f8          	cmove  %rax,%rdi
  4004fc:	48 8b 07             	mov    (%rdi),%rax
  4004ff:	c3                   	retq   

3.62

typedef enum {MODE_A, MODE_B, MODE_C, MODE_D, MODE_E}
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:
      *p1 = *p2;
      
    case MODE_E:
      result = 27;
      break;
      
    default:
      result = 12;
  }
  return result;
}

3.63

long switch_prob(long x, long n)
{
  long result = x;
  switch(n)
  {
    case 0:
    case 2:
      result += 8;
      break;
    case 3:
      result >>= 3;
      break;
    case 4:
      result = (result << 4) - x;
    case 5:
      result *= result;
    default:
      result += 0x4b;
  }
  return result;
}

3.64

A. &A[i][j][k] = Xa + L(i*S*T + j*T + k)

B. R = 7,S = 5,T = 13


3.65

A. rdx (每次移位8,即按行移動)

B. rax(每次移位120 = 8 * 15,按列移動)

C. 由B,M = 15


3.66

NR(n)是數組的行數,所以我們找循環的次數,即rdi,得到rdi = 3n.

NC(n)是數組的列數,所以我們應該找每次循環更新時對指針增加的值,這個值等於sizeof(long) * NC(n),即r8,得到r8 = 8 * (4n + 1).

綜上,可知兩個宏定義:

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

3.67

A.

B. %rsp + 64

C. 通過以%rsp作為基地址,偏移8、16、24來獲取strA s的內容(由於中間夾了一個返回地址,所以都要加8)

D. 通過傳進來的參數%rdi(%rsp + 64 + 8),以此作為基地址,偏移8、16、24來寫入strB r

E.

F. 我記得我在看《C語言程序設計: 現代方法 2rd》的時候,里面說傳遞聚合類型的變量可以使用指針,這樣比傳遞整個數據結構要快一些(當然寫操作會改變實參)。這個題目里面也都是讀操作,可以發現編譯器自動進行了優化——傳遞了基地址而非復制了整個數據結構。返回就是在調用它的函數的棧幀中存入一個相關的數據結構。(這個題里面process其實沒有棧幀,如果返回地址算eval的話)


3.68

這題考察的是內存對齊。通過結構體成員的位置逐漸縮小范圍:

  1. int t 為8(%rsi),所以4<B<=8
  2. long u 為32(%rsi),所以24 < 8 + 4 + 2*a <= 32,得到6<A<=10
  3. long y 為184(%rdi),所以176 < 4*A*B <= 184,得44 < A*B <=46。

所以AB = 45 或者AB = 46,結合A, B各自的范圍,只可能為A = 9, B = 5.


3.69

A. 根據第4、5行的指令, idx的值為(bp + 40i + 8),由第1、2行指令,這里的8是因為第一個int first整數和內存對齊的原因,所以每一個a_struct的大小為40字節。

由於0x120 - 0x8 = 280字節,所以CNT = 280/40 = 7.

B. 由第6、7行指令知,idx和x數組內元素都是signed long類型的。由於整個a_struct數據類型大小為40字節,所以其內部應該為8*5 = 8 + 8*4:

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

3.70

A.

e1.p : 0

e1.y : 8

e2.x : 0

e2.next : 8

B. 16 bytes

C.

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

3.71

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SIZE_OF_BUFFER 10

int good_echo(void)
{
	char *buffer = calloc(SIZE_OF_BUFFER, sizeof(char));
	if (buffer == NULL)
	{
		fprintf(stderr, "Error: failed to allocate buffer.\n");
		return -1;
	}
	while(1)
	{
		fgets(buffer, SIZE_OF_BUFFER, stdin);
		if (strlen(buffer) == SIZE_OF_BUFFER-1) /*兩種情況,一種是剛好輸入了能填滿緩沖區的字符數,另一種是大於緩沖區,一次不能讀完*/
		{
			fputs(buffer, stdout);
			if (buffer[SIZE_OF_BUFFER-1-1] == '\n')/*剛好輸入了能填滿緩沖區的字符數,結束讀入*/
			{
				break;
			}
			memset(buffer, 0, strlen(buffer));/*清空緩沖區,因為要通過strlen判斷讀入了多少字符,繼續讀入*/
		}
		else if (strlen(buffer) < SIZE_OF_BUFFER-1)/*一定是最后一次讀入,結束讀入*/
		{
			fputs(buffer, stdout);
			break;
		}
		else
		{
			break;
		}
	}
	free(buffer);
	return 0;
}

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

3.72

A. andq $-16, X這條指令相當於將低4位置零,也就是使得rax中保存的8n+30對16取整。所以s2-s1為8n+30對16取整的結果。

B. p的值為rsp(r8)-15對16取整的結果,確保了p數組的起始地址為16的整數倍。

C. 8n + 30對16取整有兩種可能:一種是8n本身就是16的整數倍即n = 2k,此時取整后為8n+16; 另一種是8n = 16k + 8即n = 2k + 1,此時取整后為8n + 24。由System V AMD64 ABI標准可知,s1的地址為16的整數倍(即結尾為0000),所以s2的地址也肯定是16的整數倍(結尾為0000)。又因p是由s2減15對16取整得到的結果,所以p和s2之間肯定相差2字節,即e2 = 2 bytes. 所以e1最大為(n為奇數) :8n + 24 - 16 - 8n = 8 bit, 最小為(n為偶數):8n + 16 -16 - 8n = 0.(這個題我估計沒有考慮到ABI標准對於棧幀對齊的問題,s1的地址本來就應該是16的整數倍)

D. 由A B C可知,這種方法保證了s2 和 p的起始地址為16的整數倍,而且保證了e1最小為8n,能夠存儲p數組。


浮點數部分並未測試

3.73

find_range:
	vxorps %xmm1, %xmm1, %xmm1
	vucomiss %xmm0, %xmm1
	ja .L5
	jp .L8
	movl $1, %eax
	je .L3
	.L8:
	seta %al
	movzbl %al, %eax
	addl $2, %eax
	ret
	.L5:
	movl $0, %eax
	.L3:
	rep;ret

3.74

find_range:
	vxorps %xmm1, %xmm1, %xmm1
	vucomiss %xmm0, %xmm1
	cmova $0, %eax
	cmove $1, %eax
	cmovb $2, %eax
	cmovp $3, %eax
	rep;ret

3.75

A. 每一個復數變量使用兩個%xmm寄存器傳送。

B. 通過%xmm0和%xmm1返回一個復數類型值。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM