【仅供参考】csapp第三章课后习题答案(欢迎批评指正)

3.67

【答案】

A.

%rsp + 24 z             
%rsp + 16 &z           
%rsp + 8 y            
%rsp     x            

B. 由汇编代码eval的第8行以及process的第2行可以看出传递了%rdi的值,为%rsp + 64

C. 通过%rsp + offset(偏移量)

D. 由汇编代码process可以看出通过传递给%rsp + 64 ,并从该地址开始将结构r的字段存储在栈上

E.以第一列的地址为起始地址向上存

变量是按顺序分配栈的,由此可以写出变量的位置(要注意process中的%rsp与eval中的%rsp不是同一个,比如在eval中%rsp + 8为y,在process中的第7行%rsp + 8传给%rcx,传送的值就不是y,而是把新的%rsp+8的值传给%rcx,所以应该看源代码变量名是按顺序存在栈中,确定变量名的位置,对应值也应看源代码)

栈中位置 变量名 储存值
%rsp + 80 r.q z
%rsp + 72 r.u[1] x
%rsp + 64 r.u[0] y
%rsp + 24 参数z z
%rsp + 16 s.p &z
%rsp + 8 s.a[1] y
%rsp  s.a[0] x

通过eval汇编代码的第10~12行可以看出,通过%rsp + offset 的方式访问r的元素

F. 无论是传参还是返回,结构值都是存储在栈上,而不是寄存器中。

3.68

【答案】

A=9
B=5

【解释】

根据结构体的内存对齐原则,由汇编代码第2行int相对起始地址偏移8个字节,可以得知 array数组的实际大小的字节一定大于4(否则int起始地址为4),且小于等于8,所以4

3.69

【答案】

A.CNT = 7
B.typedef struct 
    {
      long idx;
      long x[4];
    } a_struct

【解释】

A. 由汇编代码第2行0x120(288)知first与结构体数组a共占用288字节,由第6行知结构体数组按8字节对齐,所以为了对齐,first占用8字节,则结构体数组a占用280字节,由由第3~4行(bp+40i)知一个结构体a大小为40字节,所以INT = 280/40 = 7.

B. 

ap->x[ap->idx] = n;
#其实可以就是bp->a[i].x[bp->a[i].idx]
mov    %rcx, 0x10(%rax, %rdx, 8)
#所以bp->a[i].x[bp->a[i].idx]地址的计算公式为 16 + (&bp + 40i) + 8*(8 + bp + 40i)
#不妨把i设为0,方便我们观察
#所以计算地址变为 16 + &bp + 8*(8 + bp)
# 因为first占用8个字节,16 说明a的第一个参量一定是数idx ,且大小为8个字节,则(8 + bp)就表示idx,
  8 * idx 说明数组x的一个元素大小为8字节,结构体a总共占40个字节,idx占8,则数组x占32,
所以元素个数 = 32 / 8 = 4;

3.70

【答案】

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

【解释】

C.

1   proc:
2       movq    8(%rdi), %rax
        #因为这是个联合体,所以8 + rdi有两种可能,一个是up->e1.y,一个是up->e2.next;
3       movq    (%rax), %rdx
        #此处(%rax)说明%rax储存的为地址,则可以确定上面%rax为up->e2.next;
         此时%rdx有两种可能:up->e2.next->e1.p, up->e2.next->e2.x;
4       movq    (%rdx), %rdx
        #此处(%rdx)说明存储的为地址,则推出上面为up->e2.next->e1.p
5       subq    8(%rax), %rdx
        #上面分析知%rax为up->e2.next,减去8(%rax)说明8 + %rax 为数,
         则此时读取的是up->e2.next->e1.y
6       movq    %rdx, (%rdi)
        #%rdx存储的是一个数,知(%rdi)为up->x
7       ret

3.71

【答案】

1 #include 
2 #define N 100
3 void good_echo(void)
4 {    
5    char str[N];
6    while(1)
7    {
8        char* p = fgets(str,N,stdin);
9        if(p==NULL)
10       {
11           break;
12       }
13       printf("%s",p);
14   }
15    return 0;
16 }

【解释】

首先fgets的作用是将字符串读入数组,并返回该数组的首地址,所以我们用一个字符型指针指向返回值,当其不是空指针时便打印出内容,fgets不断读取文件内容一直到遇到换行或者达到最大长度时结束,下一次循环在上次位置继续读取,当读取到文件末尾时,返回一个空指针,从而结束整个循环。

3.72

【答案】

A.

当n为奇数时:s2 = s1 - (8 * n + 24)

当n为偶数时:s2 = s1 - (8 * n + 16)

B. p = 将s2向上舍入为16的倍数

C.

e1 s1
max=24 s1%16==0 n%2==1(奇数)
min=1 s1%16==1 n%2==0(偶数)

D.s2的计算方式会保留s1的偏移量为最接近16的倍数,p以16的倍数对齐。

【解释】

这道题可以仔细看一下练习题3.49

A.第五行指令leaq计算值为8n + 30,-16的位表示为 10000,所以与-16与就相当于把8n + 30向下舍入为16的倍数,当n为奇数时把8n + 30看作(8n + 8) + 16 + 6,所以向下舍入为16的倍数就是(8n + 8) + 16,即8n + 24,偶数时看作8n + 16 + 14,向下舍入为16的倍数就是8n + 16 ,然后用s1减去这个值就得到s2。

B. 第8行的leaq指令为%rsp + 15,然后下一行与-16与,其实就相当于首先给%rsp一个偏移量,然后再舍入为16的倍数,产生的效果就是向上舍入为16的倍数(这个地方不懂可以仔细看一下p73页除以2的幂向上舍入那里)

C. 结合图3-44方便理解,首先从s1到s2的空间大小上面已经知道,n为奇数时:(8 * n + 24)。n为偶数时:  (8 * n + 16),减去数组p占用的空间大小8n,剩下的空间大小(24或16)就是e1 + e2一共占用的空间大小

所以要使e1最小首先要使e1 + e2的和最小,那么n就是偶数,则e1 + e2 的大小为16,和的值确定了要使e1最小则必须使e2的值最大,e2的大小又等于p - s2,由第二问知p等于s2向上舍入成16的倍数,所以s2的地址应该是16的倍数再加1,因为向上舍入为16的倍数,就会在s2的地址上加上15得到p的地址(比如s2=17,那么p = 32, e2 = 15),则e2的大小就是15,e1 = 16 -15 = 1。

同理可以得到e1最大为24。

D.由前面的分析可以得答案。

3.73

【答案】

1 find_range:
2     vxorps %xmm1, %xmm1, %xmm1
3     vucomiss      %xmm1, %xmm0
4     jp .L1
5     ja .L2
6     jb .L3
7     je .L4
8   .L1:
9     movl $3, %eax
10    jmp  .End
11  .L2:
12    movl $2, %eax
13    jmp  .End
14  .L3:
15    movl $0, %eax
16    jmp  .End
17  .L4:
18    movl $1, %eax
19  .End:
20    rep; ret

【解释】

这里采用的方法是编写完整函数,放入一个独立的汇编代码文件,并且让汇编器和链接器将其与c语言书写的代码合并。

原来得指令生成了两次浮点常数,以及生成OTHER=3的过程,可以去掉,只需要compare 0:x,使用条件分支指令实现。

3.74

【答案】

1      vxorps    %xmm1, %xmm1, %xmm1    # %xmm1清零
2      movq      $1, %rax               # result = 1
3      movq      $2, %r8
4      movq      $0, %r9
5      movq      $3, %r10
6      vucomiss  %xmm1, %xmm0           # compare x:0
7      cmovaq    %r8, %rax"             # if > ,  result = 2
8      cmovbq    %r9, %rax              # if < ,  result = 0
9      cmovpq    %r10, %rax             # if NAN, result = 3

【解释】

要求使用条件传送,所以与上一题相比只不过把条件跳转,改成了条件传送。

3.75

【答案】

A.

第几个参数 实部 虚部
1 %xmm0 %xmm1
2 %xmm2 %xmm3
3 %xmm4 %xmm5
n %xmm(2n-2) %xmm(2n-1)

B.将返回值的实部存在%xmm0,虚部存放在%xmm1。

【解释】

A. 首先看c_imag,汇编代码第2行将%xmm1传递给%xmm0,然后返回%xmm0,说明x的虚部存放在%xmm1,再看c_real第二行直接返回,因为%xmm0为默认的保存返回值的寄存器,所以%xmm0存放的为实部。观察c_sub,因为我们知道了%xmm0为实部,所以可知%xmm2也为实部,就说明第二个参数的实部存放在%xmm2,同理可知第二个人参数的虚部存放在%xmm3,以此类推第n个参数的实部就存放在%xmm(2n-2),虚部存放在%xmm(2n-1)。

B.由c_sub的汇编代码知道%xmm0用于存放返回值的实部,%xmm1用于存放返回值的虚部。

你可能感兴趣的:(c语言)