深入理解计算机系统(第二版) 家庭作业 第三章


3.54
int decode2(int x, int y, int z)
{
    int ret;
    z -= y; //line 2
    ret = z; //line 3
    ret <<= 15;//line 4
    ret >>= 15;//line 5
    return ret*(z^x);
}

3.55
大概算法如下:
x的高32位为xh,低32位为xl。
y的符号位扩展成32位之后为ys(ys为0或者-1)。
dest_h = (xl*ys)_l + (xh*y)_l + (xl*y)_h
dest_l = (xl*y)_l
注意,所有的乘法都是unsigned*unsigned。
也就是说对于 1*(-1),如果存入两个寄存器中,那么高32位是0,低32位是-1。 
相当于 1*(UNSIGNED_MAX)。


3.56
注意n在这里是一个很小的数,用8位就能表示,也可以用n=n%256表示。

寄存器 变量
esi    x
ebx    n
edi    result
edx    mask

int loop(int x, int n)
{
    int result = 1431655765;
    int mask;
    for(mask = 1<<31; mask != 0; mask = ((unsigned)mask)>>n){
        result ^= (mask & x);
    }
    return result;
}


3.57
xp?*xp:0这个语句是不能被编译成条件传送语句的。因为如果是条件传送语句,那么不论xp为什么,*xp都会被计算。
我们要写一个和该功能完全一样的能编译成条件传送语句的函数。
于是,我们要想办法不使用*xp,而使用一个替代的指向0的非空指针。
int cread_alt(int *xp)
{
    int t = 0;
    int *p = xp?xp:&t;
    return *p;
}

3.58
MODE_A: result = *p1; *p1 = *p2; break;
MODE_B: result = *p1 + *p2; *p2 = result; break;
MODE_C: result = *p1; *p2 = 15break;
MODE_D: *p2 = *p1;
MODE_E: result = 17break;
default: result = -1break;


3.59

int switch_prob(int x, int n)
{
    int result = x;
    switch(n)
    {
        case 0x28:
        case 0x2a:
            result <<= 3break;
        case 0x2b:
            result >>= 3break;
        case 0x2c:
            result <<= 3
            result -= x;
        case 0x2d:
            result *= result;
        case 0x29//也可以不要
        default:
            result += 0x11;
            
    }
    return result;
}

中间有一句话没明白,汇编第12行 lea 0x0(%esi), %esi

3.60
对于A[R][S][T],A[i][j][k] 的位置是 A(,i*S*T+j*T+k,4)。
由汇编代码可知:
S*T = 63;
T = 9;
R*S*T = 2772/4;
所以得 R=11, S=7, T=9。

3.61
感觉可以用--j,而不是比较j和n。
int var_prod_ele(int n, int A[n][n], int B[n][n], int i, int k)
{
    int j = n-1;
    int result = 0;
    for(; j!=-1; --j)
        result += A[i][j] * B[j][k];
    return result;
}
但是这样得到的结果仍然会使用到存储器。

按下面的代码,循环里面貌似就没有用到存储器。
但是用到了一个常量4,就是增加a的时候,会add 4。
只需要result,a,e,b,4n这五个变量。

int var_prod_ele(int n, int A[n][n], int B[n][n], int i, int k)
{
    int result = 0;
    int *a = &A[i][0];
    int *b = &B[0][k];
    int *e = &A[i][n];
    for(;a!=e;)
    {
        result += *a * *b;
        b+=n;
        a++;
    }
    return result;
}

下面是其汇编代码的循环部分:
edi是4*n,ebx和edx分别是b和a,esi是e,eax是result。
ecx是用于存储乘法的寄存器。
L4:
movl (%ebx), %ecx
imull (%edx), %ecx
addl %ecx, %eax
addl %edi, %ebx
addl $4, %edx
cmpl %edx, %esi
jneL4

我怎么感觉前面那个程序,编译器应该也会自动优化。。。

3.62
M = 76/4 = 19;
i在edi中,j在ecx中;
int transpose(int M, int A[M][M])
{
    int i,j;   
    for(i=0; i<M; ++i)
    {
        int *a = &A[i][0];
        int *b = &A[0][i];
        for(j=0; j<i; ++j)
        {
            int t = *a;
            *a = *b;
            *b = t;
            ++a;
            b += M;
        }
    }
}

3.63
E1(n)在esi中,esi = 3n。
E2(n)在ebx中,ebx = 4*E2(n) = 4*(2n-1)。
所以E2(n) = 2n-1。

3.64
这道题比较考验对知识的拓展应用能力。
根据简单的推测,我们可以知道,imull的两个对象是 ebx和edx,最后edx移动到了(eax)中,所以ebx和edx一个是 *s1.p,一个是s1.v,并且word_sum的12行的eax是result的prod的地址,也就是result的地址。而eax只在第5行赋值,所以result的地址是在8(%ebp)中的。也就是说,结构体返回值实际上是利用类似参数的变量进行传入(在8(%ebp)),而传入的是返回结构体变量的地址。
所以:
A. 
8(%ebp)为result的地址。
12(%ebp)为s1.p。
16(%ebp)为s1.v。

B.栈中的内容如下表,分配的20个字节用黄底展示(每一行为4个字节)
y
x
返回地址
保存的ebp(也是当前ebp的指向)
s2.sum
s2.prod
s1.v
s1.p
&s2 (word_sum的返回值地址)


在汇编中,没懂word_sum 15: ret $4
以及diff 12: subl $4, %esp的意义何在。
可能是为了清除那个result的返回地址。

C.
传递结构体参数就像正常的传值。结构体的每一个变量可以看做是单独的参数进行传入。
D.
返回结构体的通用策略:将返回变量的地址看做第一个参数传入函数。而不是在函数中分配栈空间给一个临时变量,因为eax确实存不下一个结构体,eax充当返回变量的指针的角色。

3.65
B取四的倍数的上整数 = 8。
8+4+ (B*2)取四的倍数的上整数 = 28。
所以B的可选值为8和7。
2*A*B取四的上整数为44,所以A*B的可选值为21和22。
所以 A=3, B=7。

3.66

我们用结构体A表示a_struct。
首先,根据第11和12行,可以得到 CNT*size(A) = 196。

根据13行,知道 ecx + 4*edx + 8为 ap->x[ap->idx]的地址。
ecx存储的是bp(地址)。
ap的地址是 bp + 4 + i*size(A)
我们知道,ap->x[0] 的地址是 bp + 4 + i*size(A) + pos(x),pos(x)为结构体A中x的偏移。
那么ap->x[ap->idx] 的地址是 bp + 4 + i*size(A) + pos(x) + 4*(ap->idx)。
所以 4*edx + 8 = 4 + i*size(A) + pos(x) + 4*(ap->idx)。
所以,不难猜测,pos(x)=4,也就是说,在A中,首先是idx,再是x数组。

那么,我们看ap->idx在哪里计算过。
到第10行,edx的结果是 7i + bp[4 + 28*i],
bp[4 + 28*i]是什么呢?它很可能是bp中的a[i]的首地址。
我们先这样猜测,于是size(A) = 28,并且bp[4+28*i]的值为ap->idx。
另一方面:4*edx = 28*i + 4*bp[4+28*i] = i*size(A) + 4*(ap->idx)。
所以,我们的猜想是正确的。
因此,size(A) = 28,里面包含了一个int idx和一个数组int x[6]。
总共有多少个A呢?CNT = 196/size(A) = 7。


3.67
A. 
e1.p: 0
e1.x: 4
e2.y: 0
e2.next: 4

B.
总共需要8个字节。

C.
不难知道,赋值前后都应该是整数。
edx就是参数up(一个指针)。
最后结果是用eax - (edx)得到的,说明(edx)是整数,即up->___ 为整数,肯定是表示的e2.y。
再看看之前的eax,eax是由(eax)所得,说明到第3行后,eax是个指针。
它是由(ecx)得到的,说明ecx在第二行也是个指针。
而ecx是通过*(up+4)得到的,所以ecx是一个union指针next,即up->e2.next;
到第三行,eax为*(ecx),且是一个指针,所以eax在第三行为int* p,即up->e2.next->e1.p。
所以,赋值符号后面的表达式就为  *(up->e2.next->e1.p) - up->e2.y

再看看前面。
最终赋值的地址是 ecx+4,而ecx那时候是一个next指针,而(next+4)必须是一个int,也不难推测它是e1.x。因此前面就为 up->e2.next->e1.x。
结果如下:

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

3.68

版本一:使用getchar
void good_echo()
{
    char c;
    int x = 0;
    while( x=getchar(), x!='\n' && x!=EOF)
    {
        putchar(x);
    }
}

版本二:使用fgets
void good_echo()
{
    const int BufferSize = 10;
    char s[BufferSize];
    int i;
    while(fgets(s, BufferSize, stdin)!=NULL)
    {
        for(i=0;s[i];++i)
           putchar(s[i]);
        if(i<BufferSize-1break;
    }

    return;
}

两种方法对于EOF好像没效果,就是输入一定字符后不按回车直接按EOF,没能正确输出。
网上查到的资料说,getchar在输入字符后,如果直接按EOF,并不能退出,只能导致新一轮的输入。
需要在最开始输入的时候按,即按了回车之后按。
而fgets就不知道了,不按回车,就不添加0。

3.69
long trace(tree_ptr tp)
{
    long ret = 0;
    while(tp != NULL)
    {
        ret = tp->val;
        tp = tp->left;
    }
    return ret;
}
作用是从根一直遍历左子树,找到第一个没有左子树的节点的值。

3.70

long traverse(tree_ptr tp)
{
    long v = MAX_LONG;
    if(tp != NULL)
    {
        v = min(traverse(tp->left), traverse(tp->right)); 
            //Line16 cmovle: if(r12<rax) rax=r12;
        v = min(v, tp->v); //Line20 cmovle: if(rax>rbx) rax=rbx;
    }
    return v;
}
当然,如果要用三目条件表达式的话:
long traverse(tree_ptr tp)
{
    long v = MAX_LONG, rv, lv;
    if(tp != NULL)
    {
        lv = traverse(tp->left);
        rv = traverse(tp->right);
        v = lv < rv ? lv : rv ; //Line16 cmovle: if(r12<rax) rax=r12;
        v = v > tp->v ? tp->v : v ; //Line20 cmovle: if(rax>rbx) rax=rbx;
    }
    return v;
}

函数的目的是找到树的所有节点的值中最小的一个。



























  • 6
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值