CSAPP读书笔记与习题作业练习-第3章
- 疑问
- 习题
-
- 练习题3.1
- 练习题3.2
- 练习题3.3
- 练习题3.4
- 练习题3.5
- 练习题3.6
- 练习题3.7
- 练习题3.8
- 练习题3.9
- 练习题3.10
- 练习题3.11
- 练习题3.12
- 练习题3.13
- 练习题3.14
- 练习题3.15
- 练习题3.16
- 练习题3.17
- 练习题3.18
- 练习题3.19
- 练习题3.20
- 练习题3.21
- 练习题3.22
- 练习题3.23
- 练习题3.24
- 练习题3.25
- 练习题3.26
- 练习题3.27
- 练习题3.28
- 练习题3.29
- 练习题3.30
- 练习题3.31
- 练习题3.32
- 练习题3.33
- 练习题3.34
- 练习题3.35
- 练习题3.36
- 练习题3.37
- 练习题3.38
- 练习题3.39
- 练习题3.40
- 练习题3.41
- 练习题3.42
- 练习题3.43
- 练习题3.44
- 练习题3.45
- 练习题3.46
- 练习题3.47
- 练习题3.48
- 练习题3.49
- 练习题3.50
- 练习题3.51
- 练习题3.52
- 练习题3.53
- 练习题3.54
- 练习题3.55
- 练习题3.56
- 练习题3.57
- 整理
疑问
疑问一 练习题3.26中为什么通过循环+异或可以判断二进制数中1的个数的奇偶性(已解决)
问题:练习题3.26中通过让val不断右移1位,然后用右移的值异或val之前的值直到右移结果为0。这时最低位代表了二进制数val中1的个数的奇偶性。
回答:该运算本质上是在最低位对所有位进行异或运算,如果有奇数个1,异或的结果就是1,反之为0.
习题
练习题3.1
操作数 | 值 |
---|---|
%rax | 0x100 |
0x104 | 0xAB |
$0x108 | 0x13 |
(%rax) | 0xFF |
4(%rax) | 0xAB |
9(%rax,%rdx) | 0x11 |
260(%rcx,%rdx) | 0x13 |
0xFC(,%rcx,4) | 0xFF |
(%rax,%rdx,4) | 0x11 |
更正:$0x108是立即数,值为0x108 |
练习题3.2
movl | %eax,(%rsp) |
movq | (%rax),%dx |
movb | $0xFF,%bl |
movw | (%rsp,%rdx,4),%dl |
movq | (%rdx),%rax |
movw | %dx,(%rax) |
更正: | |
要注意目标操作数的空间大小,所以: | |
操作数(%rax),%dx的指令应为movw | |
操作数(%rsp,%rdx,4),%dl的指令应为movb |
练习题3.3
movb $0xF,(%ebx) 错误:源操作数只有4bit,不到byte
movl %rax,(%rsp) 错误:源操作数为64bit寄存器,但mov指令只移动了32bit
movw (%rax),4(%rsp) 错误:不能直接从内存移动到内存
movb %al,%sl 错误:
movq %rax,$0x123 错误:目的操作数不能是立即数
movl %eax,%rdx 错误:
movb %si,8(%rbp) 错误:
更正:
movb $0xF,(%ebx) 错误:不能使用%ebx作为地址寄存器
movl %rax,(%rsp) 错误:指令后缀和寄存器id不匹配
movw (%rax),4(%rsp) 错误:不能直接从内存移动到内存
movb %al,%sl 错误:没有叫做%sl的寄存器
movq %rax,$0x123 错误:目的操作数不能是立即数
movl %eax,%rdx 错误:目的操作数大小不正确
movb %si,8(%rbp) 错误:指令后缀和寄存器id不匹配(%si是16bit寄存器)
练习题3.4
src_t | dest_t | 指令 |
---|---|---|
long | long | movq (%rdi),%rax movq %rax,(%rsi) |
char | int | movsbl (%rdi),%eax movl %eax,(%rsi) |
char | unsigned | movsbl (%rdi),%eax movl %eax,(%rsi) |
unsigned char | long | movzbl (%rdi),%eax movq %rax,(%rsi) |
int | char | movb (%rdi) ,%al movb %al,(%rsi) |
unsigned | unsigned char | movb (%rdi) ,%al movb %al,(%rsi) |
char | short | movsbw (%rdi) ,%ax movw %ax,(%rsi) |
更正:对于高位转低位,应该存高位个字节,读低位寄存器。 | ||
所以int->char和unsigned->unsigned char的第一条指令都是movl (%rdi),%eax |
练习题3.5
void decode1(long *xp, long *yp, long *zp) {
long x = *xp;
long y = *yp;
long z = *zp;
*yp = x;
*zp = y;
*xp = z;
}
练习题3.6
表达式 | 结果 |
---|---|
leaq 6(%rax),%rdx | x+6 |
leaq (%rax,%rcx) | x+y |
leaq (%rax,%rcx,4) | x+4y |
leaq 7(%rax,%rax,8) | 7+9x |
leaq 0xA(,%rcx,4) | 10+4y |
leaq 9(%rax,%rcx,2) | 9+x+2y |
练习题3.7
long scale2(long x,long y,long z){
/**
* t=x+4x=5x
* t=t+2y=5x+2y
* t=t+8z=5x+2y+8z
*/
long t = 5*x+2*y+8*z;
return t;
}
练习题3.8
指令 | 目的 | 值 |
---|---|---|
addq %rcx,(%rax) | 0x100 | 0x100 |
subq %rdx,8(%rax) | 0x108 | 0xA8 |
imulq $16,(%rax,%rdx,8) | 0x118 | 0x110 |
incq 16(%rax) | 0x110 | 0x14 |
decq %rcx | %rcx | 0x0 |
subq %rdx,%rax | %rax | 0xFD |
练习题3.9
salq 0x4 %rax
sarl %esi %rax
更正:
salq $4 %rax
sarl %cl %rax
移位的源操作数只能是立即数或者cl寄存器
练习题3.10
long arith2(long x,long y,long z){
long t1=x|y;
long t2=t1>>8;
long t3=~t2;
long t4=z-t3;
return t4;
}
更正:t2=t1>>3
练习题3.11
A.将%rdx赋值为0
B.movq $0 %rdx
C.
xorq %rdx,%rdx(3字节)
movq $0 %rdx(7字节)
xorl %edx,%edx(2字节)
movl $0 %edx(5字节)
后两者利用了更新低4字节的指令会将高位字节置0的特性。
练习题3.12
movq %rdx, %r8
movq %rdi,%rax
xorq %rdx %rdx
divq %rsi
movq %rax,(%r8)
movq %rdx,(%rcx)
ret
练习题3.13
A.
data_t :int COMP:<
B.
data_t :short COMP:>=
C.
data_t :unsigned char COMP:<=
D.
data_t :long COMP:!=
data_t :unsigned long COMP:!=
data_t :某种指针 COMP:!=
练习题3.14
晕
练习题3.15
A.je 4003fe
B.je 400425
C.400543
400545
D.400560
练习题3.16
A.
汇编代码:
# void cond(long a,long * p)
# a in %rdi, p in %rsi
cond:
testq %rsi,%rsi 将p的值与0进行比较
je .L1 p为0时跳到L1
cmpq %rdi,(%rsi) 比较p指向的值和a
jge .L1 当p指向的值大于等于a时跳到L1
movq %rdi,(%rsi) 将a的值写入p指向的位置
.L1:
rep; ret Return
所以可以写出c代码如下:
void cond(long a,long * p) {
if (p==0)
goto L1;
if (*p>=a)
goto L1;
*p = a;
L1:
return;
}
B.因为一个if语句中有两个判断条件,所以要有两个条件分支。
练习题3.17
A.
long gotodiff_se(long x,long y) {
long result;
if (x < y)
goto x_lt_y;
ge_cnt++;
result = x - y;
return result;
x_lt_y:
lt_cnt++;
result = y - x;
return result;
}
B.运用该规则可以更直观地使用原来的判断条件。
练习题3.18
long test(long x,long y,long z){
long val = x + y + z;
if (x < -3) {
if (y>=z) {
val = y*z;
} else {
val = y*x;
}
} else if (x > 2)
val = x*z;
return val;
}
练习题3.19
A. T M P = 2 ( T r a n − T O K ) = 30 T_{MP}=2(T_{ran}-T_{OK})=30 TMP=2(Tran−TOK)=30
B. T O K + T M P = 46 T_{OK}+T_{MP}=46 TOK+TMP=46
练习题3.20
更正:补码除法用算术右移实现时要注意加上偏置量(1<<k)-1。
A.#define OP /
B.
arith:
leaq 7(%rdi),%rax 将x+7写入%rax
testq %rdi,%rdi 测试x与0的关系
cmovns %rdi,%rax 当x为非负数时将x写入%rax
sarq $3, %rax 将%rax的数算术右移3位
ret 返回
练习题3.21
long test(long x,long y){
long val = 8*x;
if (y > 0) {
if (x >= y) {
val = x & y;
} else {
val = y - x;
}
} else if (y <= -2)
val = x + y;
return val;
}
练习题3.22
A.n=12,n!=479001600
B.n=20,n!=2432902008176640000
练习题3.23
A.%rax存x,%rcx存y,%rdx存n
B.通过加载有效地址指令
C.
dw_loop:
movq %rdi,%rax 将x设为返回值
movq %rdi,%rcx 将%rcx的值置为x
imulq %rdi,%rcx #y=x*x
leaq (%rdi,%rdi),%rdx #n=2x
.L2:
leaq $1(%rcx,%rax),%rax #计算x+=y和*p++
subq $1,%rdx n--
testq $rdx,$rdx 比较n和0的关系
jg .L2 n>0时跳到L2
rep;ret return
更正:
B.编译器认为指针p总是指向x
练习题3.24
long loop_while(long a,long b){
long result = 1;
while(a < b) {
result = result * (a + b);
a= a + 1;
}
return result;
}
练习题3.25
long loop_while2(long a,long b){
long result = b;
while(b > 0) {
result = result * a;
b = b - a;
}
return result;
}
练习题3.26
A.jump to middle
B.
long fun_a(unsigned long x){
long val = 0;
while(x != 0) {
val = val ^ x;
x>>1;
}
return val & 0x1;
}
C.
当x小于32位时生成和x位数相同的全1数,超过32位时生成0xFFFFFFFF
更正:该运算用于校验x的二进制数中1的个数奇偶性
练习题3.27
long fact_for_goto(long n){
long i = 2;
long result = 1;
if (i > n)
goto done;
loop:
result *= i;
i++;
if (i <= n)
goto loop;
done:
return result;
}
练习题3.28
A.
long fun_b(unsigned long x){
long val = 0;
long i;
for(i=64;i!=0;i--) {
long y = x;
y &= 0x1;
val = (2 * val)| y;
x>>=1;
}
return val;
}
B.因为初始测试一定满足条件?
C.
更正:
long fun_b(unsigned long x){
long val = 0;
long i;
for(i=64;i!=0;i--) {
val = (val<<1) | (x&0x1);
x>>=1;
}
return val;
}
该段代码的功能是获得和x在二进制序列上呈镜像关系的数字
练习题3.29
A.因为for改为while时会把第三个一般是控制循环的表达式放到循环体的结尾,而continue会跳过这个表达式,相当于这个表达式失效了
B.continue应该被替换为跳转到循环体结尾的上一语句之前的goto语句。
练习题3.30
A.标号为-1到7
B.case 2和4、case 0和7
更正:A.标号为-1,0,1,2,4,5,7
练习题3.31
void switcher(long a,long b,long c,long* dest){
long val;
switch(a){
case 5:
c = b ^ 15;
case 0:
val = c + 112;
break;
case 2:
case 7:
val = (b+c)*4;//(b+c)<<2
break;
default:
val = b ;
}
*dest = val;
}
练习题3.32
|指令|||状态值(指令执行前)||||||
-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
标号|PC|指令|%rdi|%rsi|%rax|%rsp|* %rsp|描述
M1|0x400560|callq|10|-|-|0x7fffffffe820|-|调用first(10)
F1|0x400548|lea|10|-|-|0x7fffffffe818|0x400565|计算x+1
F2|0x40054c|sub|10|11|-|0x7fffffffe818|0x400565|计算x-1
F3|0x400550|callq|9|11|-|0x7fffffffe818|0x400565|调用last(x-1,x+1)
L1|0x400540|mov|9|11|-|0x7fffffffe810|0x400555|u=x-1
L2|0x400543|imul|9|11|9|0x7fffffffe810|0x400555|u=u*(x+1)
L3|0x400547|ret|9|11|99|0x7fffffffe810|0x400555|从last(x-1,x+1)返回99
F4|0x400555|repz retq|9|11|99|0x7fffffffe818|0x400565|从first(x)返回99
M2|0x400565|mov|9|11|99|0x7fffffffe820|-|%rdx=99
练习题3.33
1.(int a,char b,int *u, char *v)
2.(int a,byte b,int *u, byte *v)
更正:
1.(int a,short b,long *u, char *v)
2.(int b,short a,long *v, char *u)
要注意movb只是传送低8位,不代表b的类型是char,而是说明v的类型是char指针。b的类型应该通过函数的返回值6确定为2字节大小的类型。
练习题3.34
A.a0-a5
B.a6、a7
C.寄存器不够用
练习题3.35
A.x
B.
void rfun(unsigned long x){
if (x == 0)
return 0;
unsigned long nx = x>>2;
long rv = rfun(nx);
return rv + x;
}
练习题3.36
数组 | 元素大小 | 整个数组的大小 | 起始地址 | 元素i |
---|---|---|---|---|
S | 2 | 14 | x S x_S xS | x S + 2 i x_S+2i xS+2i |
T | 8 | 24 | x T x_T xT | x T + 8 i x_T+8i xT+8i |
U | 8 | 48 | x U x_U xU | x U + 8 i x_U+8i xU+8i |
V | 4 | 32 | x V x_V x |