练习题3.49
这道题挺难的,综合的知识点也挺多,特此梳理一下
push %rbp movq %rsp, %rbp1.%rbp是被调用者保存寄存器,为了防止被修改,先提前保存 2.这个步骤是设置帧指针,代码将%rbp之前的值保存到栈中。如图,帧指针跟栈指针之间的这块内存构成了栈帧。
3.设置%rbp的核心思想 (因为后面的for循环要不断修改i的值,所以要获取i的地址) 这里直接用%rbp - 8 就可以获取到i的地址
其实直接用%rsp来计算i地址应该也是可以的,书上说最近的GCC版本放弃了使用帧指针。
栈指针-16,这是一个分配内存的操作,为i分配8字节,剩余8字节未被使用。 这里开始卡住了,我一直纳闷为什么不直接放寄存器里。
p[0] = &i;主要是因为这里需要引用i的地址。
翻回P170
局部数据优先保存到寄存器中,但是以下情况保存到内存中。
寄存器不足够存放所有本地数据,比如函数参数超过6个对一个局部变量使用地址运算符’&’,因此必须为它产生一个地址某些局部变量是数组或结构,因此必须能够通过数组或结构引用访问到局部变量存储无非就是放入寄存器跟栈中,如果放入%rax这样的寄存器,在过程调用的时候值可能被覆盖,因为寄存器是过程调用共享的资源,因此寄存器的地址就没什么实际意义,所以要将带有"&"的值存到栈中。
这里的话是 8n+22 & 0x-16 0x-16 -> 111111…10000(2)
也就是把8n+22向下舍入最接近的16的倍数,n是奇数时,结果是8n+8,n是偶数时,结果是8n+16
第8-10行计算(s2+7)/8 * 8,代表将s2向上舍入到最近的8的倍数
这也是为什么n = 5, s=2065的情况下, e1 = 1, 刚好舍入到2064 然后再减去8*5 也就是p的内存大小得到2024. n为奇数,之前讲过,奇数舍入的结果是8n+8 e1分配了1,因此e2分配7
这里需要理解数组和对齐的概念,保证了指针数组的内存大小总是为8的倍数。
这道题我第二次看的时候算错了,我是按寄存器来算的;
subq %rdx, %rsi imulq %rsi, %rdi执行到第二行%rsi = y - z; 我还是把它当y来计算,以至于后面都错了
%rsi = y - z; %rdi = x * y; //=> incorrect %rdi = x * (y - z) //=> correct正确答案
long decode2(long x,long y,long z) { y = y - z; x = x * y; y <<= 63; y >>= 63; return y ^ x; }如果y-z是偶数 即000...0 -> 000...0 y-z是奇数 即000...1 -> 111...1
做题的时候让我困惑的地方
对cpto 这个指令不是很了解,一直很困惑只有一次sarq指令,也就只进行一次算术右移对于右移进行扩展的概念不理解其实就是将高位放在一个另外一个寄存器里。 假设x是四位表示的补码 原本%rsi 1111表示的是 -1 扩展以后 1111 1111还是 -1 可以根据书上的公式计算 x = 2 4 ∗ x h + x l x=2 ^4 ∗x h +x l x=24∗xh+xl
即 2 4 ∗ ( − 1 ) + 15 = − 1 2^4*(-1) + 15 = -1 24∗(−1)+15=−1
注意1111 1111 高位代表的是-1 ,低位代表的是15 因为符号位扩展到高位了。
store_prod: movq %rdx, %rax # %rax = y cqto # convert q to o,4字符号拓展到8字,假如y的符号位为1,那么%rdx所有位都是1(此时值是-1),否则,%rdx全为0(此时值是0).%rdx = yh movq %rsi, %rcx # %rcx = x sarq $63, %rcx # 将%rcx向右移63位,跟%rdx的含义一样,二进制位要么全是1,要么是0,%rcx = xh. imulq %rax, %rcx # %rcx = y * xh imulq %rsi, %rdx # %rdx = x * yh addq %rdx, %rcx # %rcx = y * xh + x * yh,计算了第二项 mulq %rsi # 无符号计算 xl*yl,并将xl*yl的128位结果的高位放在%rdx,低位放在%rax,计算了第三项. addq %rcx, %rdx # 将第二项计算结果加到%rdx movq %rax, (%rdi) # 将%rax的值放到dest的低位 movq %rdx, 8(%rdi)# 将%rdx的值放到dest的高位 ret参考的这篇答案,上面对6-8行解释的很详细。 https://blog.csdn.net/anlian523/article/details/84679282
第九行无符号计算 xl*yl,这里实在是不懂汇编怎么运算的。 参考了王爽老师的汇编语言P199。 movb 100,al movb 10,bl mul bl这里计算的是10*100 结果放入寄存器bl中 这里计算的应该是bl和bl上一行的值
对应题目6-9行 %rax -> y %rsi-> x
imulq %rax<-, %rcx imulq %rsi<-, %rdx addq %rdx, %rcx mulq %rsi # %rax * %rsi 无符号计算 xl*yl我一直以为计算的是 %rdx 和 %rsi 的值保存到%rsi,但是对应上面的第二行已经修改%rdx的值为x*yh。
小结:
cqto指令mulq指令符号扩展符号位保存到高位寄存器无符号和有符号数乘法(相同位级表示,最后的结果相同)还是这篇答案,讲的很清楚 https://blog.csdn.net/anlian523/article/details/84679282
这道题上面的答案是有问题的,不过也可能是印刷错误导致的答案不一样。
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; 这里应该是 *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; break; } return result; }二维数组 A [ S ] [ T ] A[S][T] A[S][T] 对应等式3.1 & A [ i ] [ j ] = X a + L ( T ∗ i + j ) \&A[ i ][ j ] = Xa + L(T * i + j) &A[i][j]=Xa+L(T∗i+j)
Xa是数组首位元素的地址 L是数组单位长度,如int : L = 4 T是一行的元素的个数 这时候i代表一行
三维数组 A [ R ] [ S ] [ T ] A[R][S][T] A[R][S][T]
相当于多个二维数组表格堆在一起 A [ R ] [ S ] [ T ] − > A [ R + 1 ] [ S ] [ T ] A[R][S][T] ->A[R + 1][S][T] A[R][S][T]−>A[R+1][S][T] 一个表格到另一个表格相同坐标位置相差距离为 S*T & A [ i ] [ j ] [ k ] = X a + L ( S T ∗ i + T ∗ j + R ) \&A[i][j][k] = Xa+L(ST*i + T*j+R) &A[i][j][k]=Xa+L(ST∗i+T∗j+R) 这时候i代表一个表格
call process之前
# strB process(strA s) # s in %rdi process: movq %rdi, %rax movq 24(%rsp), %rdx movq (%rdx), %rdx movq 16(%rsp), %rcx movq %rcx, (%rdi) <- 在 64(%rsp) 地址存入y movq 8(%rsp), %rcx movq %rcx, 8(%rdi) <- 在 72(%rsp) 地址存入x movq %rdx, 16(%rdi) <- 在 80(%rsp) 地址存入z retE. 实际上就是通过%rsp+偏移量来传值 F.作为函数参数和函数结果的结构体,都是使用指针传值的。
这道题没做出来主要是因为
typedef struct { int first; a_struct a[CNT]; //=> 这里是一个结构数组 int last; } b_struct;如果不了解结构数组的话,后面的汇编就提取不出有效信息。
C primer plus的例子
struct book { char title[100]; char author[100]; float value; } # struct book 相当于一种数据类型 # 声明一本书 -> int fir_book; # 声明struct book 类型的一本书 -> struct book fir_book; # 如果声明多本书 # struct book sec_book; # struct book thr_book; ... # 这样显然太麻烦,也不方便查找,使用结构数组可以解决这个问题 # struct book library[100]; # 声明一个能容纳一百个struct book类型值的数组 # 数组的大小取决于结构的大小 test: mov 0x120(%rsi), %ecx add (%rsi), %ecx lea (%rdi,%rdi,4), %rax # %rax = i*5 lea (%rsi,%rax,8), %rax # %rax = bp+i*40从这两行就能够提取到信息 a_struct的大小为40字节。
test: mov 0x120(%rsi), %ecx # %ecx = bp -> first add (%rsi), %ecx lea (%rdi,%rdi,4), %rax lea (%rsi,%rax,8), %rax注意这里的偏移量是0x120,需要转成十进制计算,我就是直接用120 - 8 (int first 字节对齐) -> 112 所以怎么都算不出结果。 实际上应该是288-8 -> 280 / 40 (a_struct) -> CNT = 7
# void test(long i, b_struct *bp) # i in %rdi, bp in %rsi test: mov 0x120(%rsi), %ecx # bp+288 匹配bp->last add (%rsi), %ecx # bp->first + bp->last lea (%rdi,%rdi,4), %rax # %rax = i*5 lea (%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中,第一个成员为整数类型的idx mov 0x8(%rax), %rdx movslq %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的整数成员idx mov %rcx, 0x10(%rax,%rdx,8)让我困惑很久的是这两句
mov 0x8(%rax), %rdx mov %rcx, 0x10(%rax,%rdx,8) // 第一行 %rdx = ap = &bp->a[i] = bp+8+i*40%rdx保存的是ap指针的地址,指向第i个结构数组,看第二句。
(,%rdx,8)一直想不明白为什么要用ap的地址*8
先看前半部分
mov %rcx, 0x10(%rax,) 16 + &bp + 40i 16怎么来的? 8字节是int first的偏移量 还有8字节应该就是idx的偏移量,这样就可以得到数组的起始位置数组位置计算公式: & a [ i ] = X a + C ∗ i \&a[i] = Xa + C*i &a[i]=Xa+C∗i Xa是起始位置,C是数组每个值的长度。 刚好对应上 (0x10 + %rax) + 8 * %rdx
所以可以得出数组的每个值长度为8字节,又是有符号数,只能是long型. 至于为什么是 8 * %rdi(&ap) 只有一种可能,就是刚好idx这个值在a_struct的最初始位置。让我困惑了很久… 之前得出a_struct的字节长度为40
typedef struct { long idx; long x[4]; } a_struct