BUPT-CSAPP 期末复习书后参考题节选及评注

收拾一个学期的学习资料,有价值的东西尽量PO出来,帮学弟学妹们少走弯路。老学长踩过的坑,希望大家就不要再踩了。
CSAPP(校内课程名是计算机系统基础,用书是CMU的《深入理解计算机系统》)让人又爱又恨

  1. 确实有用,自顶向下,从常见的C语言程序入手,到汇编,到机器码,到电路设计,褪下计算机的神秘外衣,其最终不过是经过不同层面反复抽象的电路。此课让初入茅庐者得以一窥计算机的全貌。
  2. 内容多而浅,什么都讲,百科性质的,但是又都不深入。复习时完全不知道什么是重点,只得地毯式复习,然而很多知识点也是半懂不懂,复习了好几遍又似乎啥也没干。
  3. 没有真题。

其实最终考下来卷子并没有那么难,只是复习时要面面顾及。学弟学妹们复习时可以对照着题目列表自己动手做一做。

题目列表

练习题
第2章
2.21、2.23、2.24、2.33、2.40、2.44、2.45、2.47、2.54
2.60、2.65、2.67、2.68

第3章
3.1、3.2、3.3、3.4(先阅读图3-5、3-6指令功能)、3.5、3.6
3.7、3.9、3.15、3.18、3.20、3.26、3.27、3.31、3.32、3.33
3.36、3.37、3.38、3.44、3.45

第7章
7.1、7.2、7.4、7.5

第10章
10.1、10.2、10.3、10.5
Reference:自己动手做 练习题(20190107更新).txt,是某位老师留给学生的参考资料。原文就是上面这些题号,但我并不知道是哪位,在此表示忠心感谢,对复习真的很有用。

自制评注

复习时自己一题一题做了一遍,有了一些感悟,写了下来。
没有评注的题目是因为太简单,没有啥必要评注,但做还是要做的

第2章 Bits, Bytes, and Integers

浮点数运算不满足结合律和分配律,因为舍入关系,十进制的乘法在二进制下无比巨大,float的23位尾数很快就会爆满,原先的低位会被抹掉。3+1e20-1e20=0,1e20是10^20,换成2进制有67位。
2.21 原则:不加后缀U就是有符号数,如果这个有符号数超出范围了,就先用long把它表示出来再去做截断。
有符号数和无符号数混合比较时,转换成无符号数后再比较,此时首位的负权值变正,如果之前是负数就要加上2^w
TMin=-2什么什么8,TMax=2什么什么7。
2.23
2.24 截断的数学表现是取模
2.33 除了TMin,大家的加法逆元都是自己取负,TMin的加法逆元是自己。
2.40 用移位去凑整数乘法的时候,记得移位要打括号,因为移位比四则运算的优先级低。
可以乘多了减,也可以乘少了相加。如果是负的乘数,那应该是前一项的倍数较小,减去后一项倍数较大的。
±x不算移位,奇数的乘数一定要用这俩去凑,因为为奇数的只有这俩。
2.44 condition1 || condition2要为假,则两边都应该为假,所以要找的反例应该是两者为假时的交集。
因为补码表示的正负数范围是不对称的,所以正数cast到负数一般没事,负数cast到正数就要考虑下TMin。
~y=-y-1。
要找溢出的反例,可以用算完取模的思想来找,注意要确定范围。
2.45 实数化二进制时,整数部分和小数部分要分开化。整数部分不用说,小数部分可以采取乘以2后取个位的做法。
二进制还原十进制时,需要做位权的加法。
2.47 注意在阶码全零的时候,,尾数里是没有隐含的1的,就是0.xxxxxxx。而且此时指数为1-Bias,不是-Bias。和阶码为1时是一样的。
注意 指数+Bias=阶码,所以指数=阶码-Bias,可以为负的,代表2的负数次幂。
阶码全1尾数全0的时候表示INF,根据符号位的不同可以有+0和-0。阶码全0尾数不全为0时是NaN。NaN越多,有效值就越少。
2.54 浮点数会溢出到无穷。
注意浮点数运算不满足结合律,(f+d)-f不等于d
2.60
2.65
2.67
2.68

第3章 machine-basics/controls/proccedures/data/advacned

所有的循环说到底是一回事。
3.1
3.2 注意指令的数据类型后缀只针对寄存器
3.3 注意说64位就64位,不能用32位寻址。
3.4(先阅读图3-5、3-6指令功能)先把指针处的数据按合适的形式拷贝到ax系列寄存器,再拷贝到指针的位置。
需要注意的是短有符号转长无符号时,先要转movs成长的有符号。
长数据截断时是先拷贝到长寄存器,再从低位拷出。
3.5 延续优良传统,变量初值只用来表示初值。不要吝啬中间变量。
3.6 间接寻址,注意不加$的数字是寻址,加了$才是立即数
3.7
3.9 注意,因为返回值固定在rax里,所以参数要先以某种形式拷贝到rax里
3.15 跳转指令的填充值同样可以是相对地址(PC),也可以是绝对地址。还是那句话
填充的位置 + 机器码偏移 = 实际地址
3.18 else if就是上一级if的else里套了一个if,和Python的elif不一样。
if(test) expr;的翻译:cmp/testq等设置标志位 jne/je/jge/jle done;
其中跳转时的条件和test的逻辑是反的,这样才能跳过。
利用这一特性可知:跳转指令取反是if的条件,跳转的位置是else,不跳转时进入的是if的内容。
3.20 补码负数除法的修正,C语言会自动进行修正,其原理依然是加bias之后再移位。-9/4会修正为-2,原理是加上移位数k的bias值2^(k-1)。汇编实现时,不管是正负,程序都会用leaq计算修正后的值,bias写在src的括号前,然后利用testq设置符号位,用cmovns决定是不是要覆盖,最后才进行移位。
3.26
3.27 所有的循环都会被翻译成do while型
while(test) expr;的Guard Do翻译:if(!test) goto done; expr; if(expr) goto loop;
跳到中间翻译:goto test; if(test) goto loop;
guarded-do的特征是可以跳过,跳到中间的特征是先跳到判断。
for的翻译:init-expr; while(test) expr;incr;之后看你想翻译成guarded-do还是跳到中间。
3.31 switch跳转表的翻译
首先,只要有switch就一定会执行其中的语句(不满足则去default)。一开始还没用跳转表之前的那个ja,去的就是default的位置的语句块标号。观察跳转表,同样语句块的case的跳转表条目也是一样的。
分析语句,先把.Lx标到每个case的旁边。然后按跳转表的条目依次标0,1,2,3…然后其对应的Lx对应的case的值就是这个标号。
如果做了区间平移,要还原。
3.32 每次调用callq时,栈针rsp下移八个byte(不要16进制和2进制搞不清楚),再写入该返回的下一条地址。(栈针指的是实实在在的元素而不是后一个空位)。每次调用ret时,将rsp处的地址读入rip,rsp上升8byte
3.33 记住参数写入寄存器的顺序是rdi,rsi,rdx,rcx,r8,r9,如果要压栈,则按从右往左的顺序压栈,所以常用的参数应该放在第一个。
记住rdi的低2byte是di,低1byte是dil, rsi的低1byte是sil,rdx-edx-dx-dl。
汇编在做运算时命令的后缀可以看出数据的类型,根据汇编语句上下文的衔接可以看出指针的类型。
3.36 指针的长度是long
3.37 !重要:区分指针数组和数组内元素的指针。指针数组指的是指针存储位置连续,其指向位置任意。而数组内元素的指针表示的是数组是连续的,所以指针的值是连续的。&s[2]=s+2sizeof(int), s+2=s+2sizeof(int)
传送short元素时命令的后缀是w而不是l,书上常用的q其传送的是long
3.38 多维数组中某一维加一时,其指针需要增加后面所有维度宽度的积。所以某个nested array的指针表达式是i+7j,说明其行标增加1时列表需要增加7,所以其为二维数组,第二维宽度为7。
3.44 注意当结构体作为元素参加其他结构体的对齐的时候,其长度保持不变,其对齐要求是其中最长元素的对齐要求,所以结构体作为元素时画出来可能不像个矩形,但它确实是对齐的。
3.45 注意内存是用字节编码的,但是操作系统一次能取的是8个字节,所以要使存取次数尽量少,各数据项的地址值就应该对齐到其长度的整数倍处。当所有的元素长度都是2的整数幂(含1)时,按大小降序来排可以使总大小减小。
最后结构体一定还要填充无效字节来使其整体的长度是其中最长元素的整数倍。在操作系统组织所有的数据的时候,也按其中最长的元素对齐。(比如一个结构整体长度是16,但其中最长的元素是4,就可以放到诸如0x4的地址上,这样仍然能保证每次存取都只需要一次)

第7章 Linking

链接的作用:1.解析符号 2.重定位(包含 合并节、确定定义符号的内存、修改引用符号位置的地址)
解析符号时的EDU:E指确定使用的.o文件,包含命令行中有的.o文件和在解析中确认需要从.a中抽取出来的.o文件。
D指的是已经定义的符号集合(注意弱符号也是定义)
U指的是已经被引用但是还没有定义的符号集合。(没定义指的是那些唐突出现的变量和函数,以及声明为extern的)
链接器挨个扫描命令行加入的文件,遇到.o直接加入E,并将其内的符号按规则加到D,U,遇到.a时遍历U,如果其中的某个.o可以解析U,就将其抽取出来加入E。
链接器命令行的目的仅仅是找出所有的.o加入到E,并不在乎什么强弱符号,最后只要U不为空就行。
7.1 .symtab条目:包括全局函数、全局变量,static全局函数、static全局变量
符号类型:包括global, extern, local,注意static变量都是local
节:全局变量未初始化时在COMMON(不区分时视为.bss),初始化后在.data
函数(static或者全局)都在.text
static变量未初始化时在.bss,初始化后在.data
局部变量在栈里,不属于符号,也谈不上类型、定义、节
7.2 全局符号才谈强弱,强符号至多有一个,一山不容二虎,但可以没有强符号。弱符号可以有多个。当弱符号和强符号同时存在时,所有的弱符号都解析到强符号。当只有弱符号没有强符号时,任取一个弱符号作为定义,其他同名弱符号解析到此。链接不做类型检查,会导致问题。
7.3 一般原则:每个库独立,库放在.o文件之后,注意在链接命令行的最后隐含有libc.a
需要注意的是如果库之间相互依赖,则库需要在不同的位置反复写多遍。
对命令行中一个位置的库的扫描只会进行一次,没有加入到E的.o全部被丢弃,如果还要用该库去解析后面的.o或者.a的符号,需要把库的名字再写一遍。
7.4 计算原则:下一条指令的地址 + 写在机器码内的偏移量 = 写在汇编符号旁边的实际地址
!但是,这只是一个巧合,实际在计算时取的是填充数据位置的地址+数据长度,人家问“重定位引用的地址是多少时”,不要填下一条指令的地址,而应该填填充数值的地址。
需要注意偏移量是小端序。小端序的原则是低位字节(lsb)存在低地址。注意字节之间才有大小端的关系,字节内部没有,
需要注意补码一定要是定长的,没说长度的补码毫无意义,因为无法判断首位是哪个。写在机器码内的立即数都是补码,缺位会在高位补0。
7.5 ELF段的记录中,r.offset指填充数据的位置,指的是该位置相对于所在函数的偏差。r.symbol指符号名,r.type指重定位方式(PC与否),r.addend指还剩填充的长度。
按道理 段头+r.offset-r.addend+ 机器码偏移 = 实际地址

第10章 IO

10.1 被关闭的描述符会被释放,下一次仍然可以分配(废物利用)
10.2 同一个文件被open两次,得到的打开文件表项是独立的,两者读写时的位置也是独立的
10.3 父子进程的同一个fd指向的打开文件表条目是一样的(物理一致),所以各自读写文件时会影响对方。父子进程的虚拟地址空间是一样的,也就是说刚拷贝时,父子进程看到的变量地址都相同,物理存储也相同。但是只要子进程想修改变量,就会产生中断,系统将该变量在物理上再拷贝一份,然后子进程虚拟地址指向新的拷贝。父进程修改变量不会产生新的。
也就是说,在子进程修改变量之前,两者看到的变量完全一致,子进程修改后,两者看到的变量貌同神离。
10.4 注意dup2函数的第一个参数是要指向的描述符,第二个才是会被修改的。dup2修改时,会关闭被修改描述符的打开文件条目。
10.5 dup2后两个描述符id虽然不同,但是指向的条目相同,自然文件位置也相同。

你可能感兴趣的:(BUPT-CSAPP,2019,Fall)