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 | n |
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用于存放返回值的虚部。