简述

相信大部分人在做这些题的时候,因为书中没有给答案,而去网上找参考答案,比如那些高阅读量的博客和git。当然,我也是这样,但他们的答案中还是有好多错误,比如3.59他们几乎都没讲清楚提示中的公式怎么来的,3.60中对移位操作中对%cl的读取,等等。。希望读者们在阅读这些文章时,要带着自己的思想和疑问去理解,而不是一味地觉得答案就肯定是对的,当然,本文有任何错误,也欢迎各位指出。

3.58

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

y先左移63位,再右移63位,如果之前y是奇数,那么y的二进制全是1;y是偶数,那么y的二进制全是0.

3.59


首先讲解一下,提示里的公式x=264∗xh+xlx=2^{64}*x_h+x_lx=264∗xh​+xl​,之所以可以这么写是因为符号拓展,以4位二进制int为例:
1111的补码数,为-1.将其进行符号拓展后为1111 1111,其值也为-1,但这里可以将1111 1111写为高位1111的补码数 * 242^424 + 低位1111的无符号数:
即-1 * 242^424 + 15 = -1.

原理:%rdx和%rax的二进制连起来表示这个数,既然连起来了,符号位就跑到了%rdx的最高位了,除符号位权值为负外,其余位的权值均为正。所以,高位寄存器%rdx当做补码数,低位寄存器%rax当做无符号数。因为符号位现在在高位寄存器那儿呢,所以高位寄存器当做补码数了;而低位寄存器的每一位的权值现在都是正的了,所以低位寄存器要当做无符号数。

所以xlx_lxl​为T2U(x)T2U(x)T2U(x)即x的二进制表示作为无符号数。xlx_lxl​与xxx有相同的位级表示。
xhx_hxh​,当原数符号位为1,64位二进制位上全为1,其值为-1;当原数符号位为0时,64位二进制位上全为0,其值为0。

再讲解一下本文用到的数学公式:有x=264∗xh+xlx=2^{64}*x_h+x_lx=264∗xh​+xl​和y=264∗yh+yly=2^{64}*y_h+y_ly=264∗yh​+yl​,那么有:
x∗y=(264∗xh+xl)∗(264∗yh+yl)x*y=(2^{64}*x_h+x_l)*(2^{64}*y_h+y_l)x∗y=(264∗xh​+xl​)∗(264∗yh​+yl​)
=xhyh2128+(xhyl+xlyh)264+xlyl=x_hy_h2^{128}+(x_hy_l+x_ly_h)2^{64}+x_ly_l=xh​yh​2128+(xh​yl​+xl​yh​)264+xl​yl​
但这个公式其实并不陌生,它与2.3.5补码乘法(P67) 里面的公式2.18有异曲同工之妙,另外理解本题需要阅读此节。

第一项xhyh2128x_hy_h2^{128}xh​yh​2128肯定溢出,双寄存器都装不下,截断后全为0,忽略。

关于第二项,(xhyl+xlyh)(x_hy_l+x_ly_h)(xh​yl​+xl​yh​)这个数值是需要放在高位寄存器中的(因为这一项乘以的数为2642^{64}264),假设xhylx_hy_lxh​yl​分别是-1和UMAX,仅仅是它俩的乘积都会使得高位寄存器溢出(考虑补码数和无符号数的表示范围就能想到),如果溢出,放入高位寄存器时会自行截断。

第三项xlylx_ly_lxl​yl​,直接使用双寄存器来保存结果。

下面开始讲解汇编代码:
第一个参数*dest在%rdi中,第二个参数x在%rsi中,第三个参数y在%rdx中。

store_prod:movq   %rdx, %rax   # %rax = ycqto                # convert q to o,4字符号拓展到8字,假如y的符号位为1,那么%rdx所有位都是1(此时值是-1),否则,%rdx全为0(此时值是0).%rdx = yhmovq   %rsi, %rcx   # %rcx = xsarq   $63,  %rcx   # 将%rcx向右移63位,跟%rdx的含义一样,二进制位要么全是1,要么是0,%rcx = xh.imulq  %rax, %rcx   # %rcx = y * xhimulq  %rsi, %rdx   # %rdx = x * yhaddq   %rdx, %rcx   # %rcx = y * xh + x * yh,计算了第二项mulq   %rsi         # 无符号计算 xl*yl,并将xl*yl的128位结果的高位放在%rdx,低位放在%rax,计算了第三项.addq   %rcx, %rdx   # 将第二项计算结果加到%rdxmovq   %rax, (%rdi) # 将%rax的值放到dest的低位movq   %rdx, 8(%rdi)# 将%rdx的值放到dest的高位ret

重点讲一下6-8行,发现这里代码计算的是(xhy+xyh)(x_hy+xy_h)(xh​y+xyh​),而数学公式里面要求是(xhyl+xlyh)(x_hy_l+x_ly_h)(xh​yl​+xl​yh​),之所以汇编要如此计算,是利用了相同的位级向量,无论用无符号数乘法还是补码乘法,其结果的截断的位级表示肯定是一样的。

但这里有点不一样,给定x⃗\vec xx和y⃗\vec yy​两个位级向量,固定将x⃗\vec xx看作补码数,而将y⃗\vec yy​分别看作补码数和无符号数,那么x与y的两种乘积的截断的位级表示是一样的。接下来用个小例子来证明该结论。(注意代码是将乘积的截断的位级表示看作补码数的)
假设整数类型为3位,x⃗\vec xx和y⃗\vec yy​分别为111111,x的值为-1,而y的值分别为-1,7.
首先看-1 * -1 = 1,那么位级表示为001
再看-1 * 7 = -7,那么位级表示为1001,截断后为001
证毕。

考虑下第9行是否会溢出,无符号数最大为264−12^{64}-1264−1,所以两个无符号数的乘积最大为(264−1)2(2^{64}-1)^2(264−1)2等于2128+1−2652^{128}+1-2^{65}2128+1−265.而128位的补码数的最大范围为2127−12^{127}-12127−1.
而(2128+1−265)−(2127−1)(2^{128}+1-2^{65})-(2^{127}-1)(2128+1−265)−(2127−1) = 2127+2−2652^{127}+2-2^{65}2127+2−265 > 0,所以可能溢出。

3.60

long loop(long x,int n)
{long result = 0;long mask;for(mask = 1;maks != 0;mask=mask << (n % 64))//如果这里不能保证是正余数(0-63)的话,就用下面的写法{result |= (x & mask);}return result;
}

这里难点主要在于salq %cl, %rdx这里的移位量到底是多少,根据移位操作中的解释,因为被移位数为64位二进制(26=642^6 = 6426=64),所以只看%cl的低6位,或者循环的执行可以改为mask=mask<<(n & 0x3F)

3.61


首先看上图c语句与其汇编语句的对应(3.6.6节),题目要求新函数对应的汇编代码也会用到条件传送,即要求有三目表达式。对于第4行,看起来可能是多余的,但3.6.6节讲到条件传送中,第一个操作数可以是源寄存器或者内存地址,所以立即数是不可以,所以这里多了一步。

如果函数改成long cread_alt(long *xp) { return (!xp ? 0 : *xp); },那么汇编代码可能是:

cread_alt:movl $0, %eaxtestq %rdi, %rdicmovne (%rdi), %rax #直接传送ret

当然也可以改成如下:

long cread_alt(long *xp)
{long t = 0;long *p = xp ? xp : &t; //得到xp指针或者0的地址,这句转换为条件传送语句后,也不会可能去读取空指针return *p; //解引用,现在读取指针指向值肯定不会出错
}

为了验证汇编代码,本人用MinGW进行了编译,使用命令gcc -Og -S test.c,c文件内容为long cread(long *xp) { return (xp ? *xp : 0); },发现不管优化程度是多少,生成汇编基本都是(发现并没有使用条件传送,且没怎么看懂):

LFB0:movl    4(%esp), %eax #得到了xp指针testl %eax, %eax je   L3movl  (%eax), %eax #指针不为空,读取指针指向的值ret
L3:xorl %eax, %eaxret

3.62

锻炼你的反向工程能力。注意有的语句可以简化,不用非得照着汇编原封不动翻译。

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:*p1 = *p1 + *p2;result = *p1;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;break;}return result;
}

3.63

0000000000400590<switch_prob>:400590: 48 83 ee 3c    sub $0x3c, %rsi #n -= 60,说明最后n的实际数要加60400594: 48 83 fe 05    cmp $0x5, %rsi #比较n > 5400598: 77 29          ja  4005c3 <switch_prob+0x33> #如果n > 5那么跳转到default# 所以n <= 5的情况就只有交给跳转表处理40059a: ff 24 f5 f8 06 40 00   jmpq *0x4006f8(,%rsi,8) #间接跳转到0x4006f8 + 8*n# 跳到跳转表对应的位置,从跳转表来看,n的取值只能是0-5,因为只有6个八字节# 0和2会跳到这个位置4005a1: 48 8d 04 fd 00 00 00   lea  0x0(,%rdi,8),%rax4005a8: 00400593: c3             retq# 3会跳到这个位置4005aa: 48 89 f8       mov %rdi, %rax4005ad: 48 c1 f8 03    sar $0x3, %rax4005b1: c3             retq# 4会跳到这个位置4005b2: 48 89 f8       mov %rdi, %rax4005b5: 48 c1 e0 04    shl $0x4, %rax4005b9: 48 29 f8       sub %rdi, %rax4005bc: 48 89 c7       mov %rax, %rdi# 5会跳到这个位置4005bf: 48 0f af ff    imul %rdi, %rdi# 大于5和1会跳到这个位置4005c3: 48 8d 47 4b    lea 0x4b(%rdi), %rax4005c7: c3             retq

而且从汇编代码来看,如果n的值是<60,那么n-60<0,那么汇编代码就会执行到jmpq *0x4006f8(,%rsi,8),本来应该跳转到这6个八字节,但最终间接跳转到非法的八字节。但也许此题重点不在于此,应假设n>=60.

long switch_prob(long x, long n){long result = x;switch(n):{case 60:case 62:result = x * 8;break;case 63:result = result >> 3;break;case 64:result = (result << 4) - x;x = result;case 65:x = x * x;//注意64,65后面没有breakdefault:result = x + 75;}
}

3.64

假设有数组D[S][T]D[S][T]D[S][T],等式3.1为D+L(T⋅i+j)D+L(T \cdot i+j)D+L(T⋅i+j),这里T明显为列数,更加深入的说,代表第一维度中每个维度的元素个数。
假设有数组D[R][S][T]D[R][S][T]D[R][S][T],等式3.1应为D+L(ST⋅i+T⋅j+k)D+L(ST \cdot i+ T \cdot j + k)D+L(ST⋅i+T⋅j+k),ST为第一维度中每个维度的元素个数。

store_ele:leaq  (%rsi, %rsi, 2), %rax  # %rax = 3 * jleaq  (%rsi, %rax, 4), %rax  # %rax = j + 4(3j) = 13 * jleaq  %rdi, %rsi             # %rsi = isalq  $6, %rsi               # %rsi * = 64addq  %rsi, %rdi             # %rdi = 65 * iaddq  %rax, %rdi             # %rdi = 65 * i + 13 * jaddq  %rdi, %rdx             # %rdx = 65 * i + 13 * j + kmovq  A(, %rdx, 8), %rax     # %rax = A + 8 * (65 * i + 13 * j + k)movq  %rax, (%rcx)           # *dest = A[65 * i + 13 * j + k]movl  $3640, %eax            # sizeof(A) = 3640ret

则有:

S * T = 65
T = 13
S * T * R * 8 = 3640

得到:R = 7 ; S = 5 ; T = 13

3.65

.L6:movq  (%rdx), %rcx  # t1 = A[i][j]movq  (%rax), %rsi  # t2 = A[j][i]movq  %rsi, (%rdx)  # A[i][j] = t2movq  %rcx, (%rax)  # A[j][i] = t1addq  $8, %rdx      # A[i][j] -> A[i][j+1]addq  $120, %rax    # A[j][i] -> A[j+1][i], 120 == 8*Mcmpq  %rdi, %rax    jne   .L6           # if A[j][i] != A[M][M]

A.从第6行就能看出来%rdx是A[i][j],因为每次只加8,即一个元素大小。
B.因为寄存器%rdx是A[i][j],所以另一个寄存器%rax是A[j][i]。
C.根据公式,120 == 8*M,所以M为15.

3.66

sum_col:leaq   1(, %rdi, 4), %r8        # %r8 = 4 * n + 1leaq   (%rdi, %rdi, 2), %rax    # result = 3 * nmovq   %rax, %rdi               # %rdi = 3 * ntestq  %rax, %raxjle    .L4                      # if %rax <= 0, goto L4salq   $3, %r8                  # %r8 = 8 * (4 * n + 1)leaq   (%rsi, %rdx, 8), %rcx    # %rcx = A[0][j]的地址movl   $0, %eax                 # result = 0movl   $0, %edx                 # i = 0
.L3:addq   (%rcx), %rax             # result += A[i][j]addq   $1, %rdx                 # i += 1addq   %r8, %rcx                # 这里每次+8*(4n+1),说明每一行有4n+1个,因此NC(n)为4*n+1cmpq   %rdi, %rdx               jne    .L3                      # 当%rdx等于3*n才循环结束,所以可以说明一共有3n行,因此NR(n)为3*nrep; ret
.L4:movl $0, %eaxret

所以有NR(n) = 3 * n; NC(n) = 4 * n + 1;

3.67

# strB process(strA s)
# s in %rdi
process:movq %rdi, %rax        #第一个参数作为返回值,即要返回的结构体的开始地址movq 24(%rsp), %rdx  #栈指针开始的第4个八字节的内容,存入%rdx,内容为结构体A的第二个成员:指针pmovq (%rdx), %rdx      #读取指针p指向的long型对象,再存入%rdxmovq 16(%rsp), %rcx    #栈指针开始的第3个八字节的内容,内容为结构体A的成员数组的第2个元素:D[1]movq %rcx, (%rdi)      #将D[1],存入返回结构体的第1个八字节movq 8(%rsp), %rcx     #栈指针开始的第2个八字节的内容,内容为结构体A的成员数组的第1个元素:D[0]movq %rcx, 8(%rdi)     #将D[0],存入返回结构体的第2个八字节movq %rdx, 16(%rdi)    #将long型对象,存入返回结构体的第3个八字节#栈指针开始的第1个八字节,这里并没有使用,因为存的是调用后的返回地址ret
# long eval(long x, long y, long z)
# x in %rdi, y in %rsi, z in %rdx
eval:subq $104, %rsp       #为栈分配了13*8字节空间,即13个八字节movq %rdx, 24(%rsp)   #z存入栈指针开始的第4个八字节leaq 24(%rsp), %rax   #栈指针开始的第4个八字节中的第一个字节的地址,存入%rax,作为结构体A的指针成员pmovq %rdi, (%rsp)     #x存入栈指针开始的第1个八字节movq %rsi, 8(%rsp)    #y存入栈指针开始的第2个八字节movq %rax, 16(%rsp)   #p存入栈指针开始的第3个八字节leaq 64(%rsp), %rdi   #栈指针开始的第9个八字节,的开始地址call process          #这里有隐藏操作,分配八字节栈空间,存入返回地址,即下一行代码地址movq 72(%rsp), %rax   #这三行汇编执行加法addq 64(%rsp), %raxaddq 80(%rsp), %raxaddq $104, %rsp       #回收栈空间ret

A.

注意此图中,从下往上是地址增加方向。
B.
传递了%rsp+64,即栈指针开始的第9个八字节,的开始地址。
C.
因为结构参数s存在栈空间里,所以用%rsp+偏移量来访问的。
D.
r的空间是分配在栈空间里,所以也是%rsp+偏移量来设置的。
E.

F.
结构体作为参数传入和返回时,都是以指针来传递。

3.68

从汇编movslq 8(%rsi), %rax中,可以看出结构体str2中int t是从第2个八字节开始:

左边为最大情况,右边为最小情况。在最小情况中,如果数组再少一个元素,即数组大小由5字节变成4字节,那么int变量就会跑到第1个八字节中去了。
所以5<=B<=8.

从汇编addq 32(%rsi), %rax中,可以看出结构体str2中long u是从第5个八字节开始:

左边为最大情况,右边为最小情况。
所以7<=A<=10.

从汇编movq %rax, 184(%rdi)中,184既可能是最大情况,也可能是8字节补齐情况。
所以184-8<A*B*4<=184.

答案唯一解为:A=9; B=5;

3.69

从c语句ap->x[ap->idx] = n;知道a_struct的两个成员分别是数组和整数类型。

# void test(long i, b_struct *bp)
# i in %rdi, bp in %rsi
test:mov 0x120(%rsi), %ecx         # bp+288 匹配bp->lastadd (%rsi), %ecx              # bp->first + bp->lastlea (%rdi,%rdi,4), %rax       # %rax = i*5lea (%rsi,%rax,8), %rax       # %rax = bp+i*40# ap = &bp->a[i] = bp+8+i*40, +8意味着从bp开始的第1个八字节里面只有int,且a_struct大小必为8字节或更大,若为4字节,就不是+8而是+4了# 因为是i*40,所以a_struct大小为40字节# 此句很明显取出了一个数,再结合倒数第二条指令mov %rcx, 0x10(%rax,%rdx,8),所以%rdx为ap->idx# 而且在结构体a_struct中,第一个成员为整数类型的idxmov 0x8(%rax), %rdxmovslq %ecx, %rcx             # mov时符号拓展成4字8字节# 先看0x10(%rax,)部分,是bp+16+i*40,比ap多了8字节,这里是a_struct数组成员的开始地址,也说明了idx大小为8字节# 再看(,%rdx,8)部分,是idx*8,所以说明了a_struct数组成员的大小为8字节# 合起来看就是bp+8+i*40+8 +idx*8,第二个+8跳过了a_struct的整数成员idxmov %rcx, 0x10(%rax,%rdx,8)# a_struct大小为40字节,第一个成员idx为long,8字节,还剩32字节# 第二个成员是long型数组,按照剩余字节,数组大小为4retq

A.
因为7*40 + 8 = 288 = 0x120,所以CNT=7,要推出CNT必须先推理出a_struct的大小。
B.

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

3.70

   proc:movq    8(%rdi), %rax  #偏移量为8,存的是up->e1.y或者是up->e2.nextmovq    (%rax), %rdx   #用作内存引用,所以上面是up->e2.next,取出*(up->e2.next)的偏移量为0的内容,也有两种情况movq    (%rdx), %rdx   #用作内存引用,所以上面是*(up->e2.next).e1.p,取出*( *(up->e2.next).e1.p )的内容,为long型subq    8(%rax), %rdx  #取出*(up->e2.next)的偏移量为8的内容,因为要作为减数,所以减数是*(up->e2.next).e1.ymovq    %rdx, (%rdi)   #将减法之差存入,up->e2.xret

A.

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

B.
16
C.
up->e2.x = *( *(up->e2.next).e1.p ) - *(up->e2.next).e1.y,具体看注释。

3.71

这道题主要需要了解fgets函数(char * fgets ( char * str, int num, FILE * stream );)。下面将fgets函数的api文档进行翻译。

Reads characters from stream and stores them as a C string into str until (num-1) characters have been read or either a newline or the end-of-file is reached, whichever happens first.
A newline character makes fgets stop reading, but it is considered a valid character by the function and included in the string copied to str.
A terminating null character is automatically appended after the characters copied to str.
Notice that fgets is quite different from gets: not only fgets accepts a stream argument, but also allows to specify the maximum size of str and includes in the string any ending newline character.

从流中读取字符,并将它们作为C string存储进str参数中,直到num-1个字符已经被读取,或者是到达新行或者EOF,这三个条件谁先到达都会使得读取停止。
换行字符使得fgets函数停止读取,不过换行符也会被当做一个合法字符来读取。
一个空字符将会自动加在读取的字符后,然后再复制给str。

On success, the function returns str.
If the end-of-file is encountered while attempting to read a character, the eof indicator is set (feof). If this happens before any characters could be read, the pointer returned is a null pointer (and the contents of str remain unchanged).
If a read error occurs, the error indicator (ferror) is set and a null pointer is also returned (but the contents pointed by str may have changed).

当函数执行成功,返回str。
当读取字符时遇到一个EOF时,EOF标识符被设置。如果在任何字符都没有进行读取时,就发生了这样的事,那么返回空指针(str指向的文本保持不变)。如果发生了读取错误,那么error标识符被设置,也返回空指针(但str指向的文本可能会改变)。

#include <stdio.h>
#include <assert.h>
#define BUF_SIZE 12void good_echo(void) {char buf[BUF_SIZE];while(1) {char* p = fgets(buf, BUF_SIZE, stdin);if (p == NULL) {//这里需要改break;}printf("%s", p);}return;
}

1.根据翻译得知,使用fgets函数便可以保证“当输入字符超过缓冲区空间大小时,也能正常工作”。
2.关于“你的代码还应该检查错误条件,在遇到错误条件时返回”这点,其实判断条件if (p == NULL)太笼统了,可以通过ferror函数(int ferror ( FILE * stream );)来判断(stdin的类型是FILE *),当读取出错时,调用ferror函数返回非0值,上述代码应写成if ( (p == NULL) & (ferror(stdin) != 0) )

3.72

此题与练习题3.49几乎一模一样,具体讲解请看此篇博客。
注意c语句long **p = alloca(n * sizeof(long*));,p的类型为long **即long指针的指针,可以这么理解,分配long型数组时,返回long *指针;当分配long *型数组时,返回long **指针。

第5行%rax存的是30+8n。
第6行分为两种情况:(and -16解释为向下取整到16的倍数)
a.当为偶数时,分成8n和30两部分,8n and -16得8n,30 and -16得16.
b.当为奇数时,分成8(n-1)和38两部分,8(n-1) and -16得8(n-1),38 and -16得32.
第8行加上偏置15(24−12^4-124−1),第9行 and -16,执行完这两行,就相当于向上取整到16的倍数。注意在练习题3.49中,andq $-16, %r8这句是通过两句汇编来实现的(先右移再左移,而本题是直接and -16)。
A.
s2=s1−((8∗n+30)&amp;0xfffffff0)s_2 = s_1 - ((8 * n + 30) \&amp; 0xfffffff0)s2​=s1​−((8∗n+30)&0xfffffff0),根据上面的分析:
当n为偶数时,s2=s1−(8∗n+16)s_2 = s_1 - (8 * n + 16)s2​=s1​−(8∗n+16)
当n为奇数时,s2=s1−(8∗n+24)s_2 = s_1 - (8 * n + 24)s2​=s1​−(8∗n+24)

B.
p=(s2+15)&amp;0xfffffff0p = (s_2 + 15) \&amp; 0xfffffff0p=(s2​+15)&0xfffffff0

C.
大方向分为,当s2s_2s2​为16的倍数(这种情况p数组就直接从s2s_2s2​开始分配),和s2s_2s2​不为16的倍数(这种情况p数组还需要向地址增加方向滑动1-15个字节)。

1.因为e1和e2是用来滑动的,所以当e2为0,即s2s_2s2​为16的倍数时,当e1就会最大。再看当n为奇数时,分配数组空间为8 * n + 24,多出来24字节空间作为e1。e1最大为24,此时s2s_2s2​为16的倍数,且n为奇数。

2.当s2s_2s2​不为16的倍数时,p数组空间需要滑动来16对齐,当s2s_2s2​%16=1时,向地址增加方向滑动15个字节,此时达到最大滑动距离了,即e2=15。而e1=可滑动空间-e2,当n为偶数时,滑动空间为16字节,则e1=可滑动空间-e2=16-15=1。e1最小为1,此时s2s_2s2​%16=1,且n为偶数。

D.
p数组空间是16对齐的。
s2s_2s2​是容下8 * n字节的最小的16的倍数再加16。

3.73

原书中的汇编即图3-51中的汇编,确实很乱,这样改完之后清爽多了。

find_range:vxorps %xmm1, %xmm1, %xmm1vucomiss %xmm1, %xmm0jp .L1ja .L2jb .L3je .L4.L2:movl $2, %eaxret.L3:movl $0, %eaxret.L4:movl $1, %eaxret.L1:movl $3, %eaxrep; ret

3.74

这样的话,连cmovp都不需要用了。

find_range:vxorps %xmm1, %xmm1, %xmm1movq $0, %r8movq $1, %r9movq $2, %r10movq $3, %raxvucomiss %xmm1, %xmm0cmovb %r8, %raxcmove %r9, %raxcmova %r10, %raxret



可以看出在比较大于小于时,有两套指令可以用,但因为比较浮点数用到的标志位为CF和ZF,所以再看上表,则应该使用下面这套指令。

3.75

A.

第n个参数 real img
1 %xmm0 %xmm1
2 %xmm2 %xmm3
3 %xmm4 %xmm5
n %xmm(2n-2) %xmm(2n-1)

B.
imag部分返回值在%xmm1, real部分返回值在%xmm0.

《深入理解计算机系统》第三版 第三章家庭作业答案相关推荐

  1. 计算机网络安全教程(第三版)第一章简答题答案

    第 1 章 网络安全概述与环境配置 网络攻击和防御分别包括哪些内容? 答: 攻击技术主要包括以下几个方面. (1)网络监听:自己不主动去攻击别人,而是在计算机上设置一个程序去监听目标计算机与其他计算机 ...

  2. 编译原理陈火旺第三版第七章课后题答案

    下面的答案仅供参考! 1. 给出下面表达式的逆波兰表示(后缀式): a*(-b+c)                               not A or not (C or not D) a ...

  3. 编译原理陈火旺第三版第六章课后题答案

    下面的答案仅供参考! 1.按照表6.1所示的属性文法,构造表达式(4*7+1) *2的附注语法树. 答: 首先考虑最底最左边的内部结点,它对应于产生式F→digit,相应的语义规则为F. val: = ...

  4. 微机原理与接口技术[第三版]——第五章课后习题答案

    5. 1k×1--16k×8 (1)1位变8位,一组需要8个芯片.1k变16k,需要16组芯片,因此一共128芯片. (2)1k=,需要10位片内寻址. (3)16=,需要4位片选信号. 6. 4K× ...

  5. 深入理解计算机系统(第二版)第四章知识整理

    深入理解计算机系统(第二版)笔记 第四章 处理器体系结构 4.1 Y86指令集体系结构 4.1.1 程序员可见的状态 4.1.2 Y86指令 4.1.3 指令编码 4.1.4 Y86异常 4.1.5 ...

  6. 计算机网络(第三版)胡亮 课后习题一答案

    计算机网络(第三版)胡亮 课后习题一答案 1.计算机网络所涉及的两大主要技术是什么? 2.什么是计算机网络?计算机网络具有哪些功能? 3.举例说明计算机网络有哪些应用? 4.什么是通信子网?什么是资源 ...

  7. 深入理解计算机系统 -资料整理 高清中文版_在所不辞的博客-CSDN博客_深入理解计算机系统第四版pdf

    深入理解计算机系统 -资料整理 高清中文版_在所不辞的博客-CSDN博客_深入理解计算机系统第四版pdf

  8. 算法导论第三版第十一章11.1-4

    算法导论第三版第十一章11.1-4 我们希望在一个非常大的数组上,通过利用直接寻址的方式来实现一个字典.开始时,该数组中可能包含一些无用信息,但要堆整个数组进行初始化时不太实际的,因为该数组的规模太大 ...

  9. java第二版课后题答案_Java语言程序设计第2版第16章 课后习题答案

    <Java语言程序设计第2版第16章 课后习题答案>由会员分享,可在线阅读,更多相关<Java语言程序设计第2版第16章 课后习题答案(62页珍藏版)>请在人人文库网上搜索. ...

最新文章

  1. webpack url-loader limit 转换部分资源为base64格式 其余不转换
  2. 为什么我们很难看到代码 5 分钟前的样子?
  3. 《按键消抖与LED控制》实验的个人思考与总结
  4. MAVEN项目环境搭建
  5. 程序员如何更快的工作之 SQL Server
  6. 定期存单丢了被别人捡到,里面的存款会不会被领走?
  7. emacs python_Emacs之Python编程环境配置 - elpy
  8. mysql 之 优化 (收集于网络)
  9. Linux下DMA添加两个channel,基于Linux嵌入式系统的ISA总线DMA的实现
  10. 域渗透——Local Administrator Password Solution
  11. 旅游景点宣传画册PPT模板
  12. mysql 事务补偿_分布式事务之消息补偿解决方案
  13. EfficientDet 论文记录
  14. java 解析dataset_C# DataSet用法的详细解析|C#教程
  15. js获取时间戳的几种方式
  16. M6A rna甲基化最新研究成果进展、论文合集(2021年)
  17. twemproxy0.4原理分析-批量操作(mset,hset等)的实现原理
  18. 揭秘空手套白狼的灰色产业,人性背后的暴利
  19. with admin option和with grant option的区别与用法
  20. 应用数学是关于计算机,应用数学学科

热门文章

  1. 羊皮卷之十 主啊,指引我
  2. 当人工智能渐渐成熟,健康医疗将更专注于数据整合
  3. 【小程序,h5页面】 踩坑之路
  4. cp symbolic links 引发Too many levels of symbolic links
  5. H5前端,正方体动画效果
  6. 腾讯会议可以录屏吗?学习两个录制会议的好方法
  7. 两种动态灰狼优化算法
  8. linux 系统加载优盘
  9. IDEA 方法返回值和返回类型自动补全快捷键设置
  10. 华为认证有哪些级别分类?