CSAPP-Revision-ch03

Posted temporary2021.6期末复习版

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CSAPP-Revision-ch03相关的知识,希望对你有一定的参考价值。

预计第三章会在第四次完全结束。
今天先跳过“栈帧结构”一节,因为发现作业题里好多关于“结构体 struct”和联合体“Union”部分的。

昨日循环复习

循环是很重要的一个部分,其已经相对是指令集合的一个使用了。而在这前面基本都是单条指令的学习和运用。
下面来看这样一道题:

在这里插入图片描述
和昨日的有异曲同工之妙,但刚好题型是反过来的,我们需要写汇编。

我们一开始让栈指针rsp减了0x10,就是十进制下的16。
其实就是刚好是4int数据,4 × sizeof(int) = 4 * 4 = 16
所以接下来就是连续的四条指令。
把2放到%rsp
0放到%rsp + 4
1放到%rsp + 8
9放到%rsp + 12
据此可以完成前面的填空

接着继续往下看

从jl指令可以看出前边应该是一条比较的指令,那么一定是i < 4的汇编
再又有,%rdi就是存储sum指针的寄存器,要对应其内存取值才是*sum的值。
将%ecx拿来加说明了%ecx应该是拿来存放a[i]的。
我们可以那eax来当做i循环变量处理。

因此中间三条要做的操作就是,将当前arr[i]数转移到%ecx中,执行i++的操作,然后另i和4做一次比较
那么最后呢?
最后我们需要释放栈空间,只要让栈指针加回0x10就可以啦。
我的答案如下:

func: 
subq	  $0x10, %rsp					#   为arr数组开辟空间                 
movl	  $0x09, 12(%rsp)				#                              
movl   $0x1,  8(%rsp)                
movl   $0x0,  4(%rsp)             	#                              
movl	  $0x2, (%rsp)					#                              
movl	  $0, %eax
movl   $0, %ecx
.L1
   movl  (%rsp, %eax, 4), %ecx                              
   addl   $0x1, %eax                             
   cmpl  %eax, $0x4                            
jl 	  .L1 
  addl   %ecx, (%rdi)					#                              
  addq  $0x10, %rsp                             
ret

SWITCH跳转表

或许大家在C语言中已经习惯性地用if elif else来替代switch case default。
但不可否认switch仍旧是比较重要的一种语法(其实现也是非常简单高效的),接下来就来看看我们的汇编是如何来实现switch语句的。
语言也比较难以描述这张表,所以利用图示来表示:
在这里插入图片描述
就是说从我们的汇编翻译回C的时候,我们就是把各种情况(即图中的各种Tar)视为了一个数组结构,我们把x当作下标索引跳转到要执行的代码段。
再来看更直观的一张图:
在这里插入图片描述
图中,如果x > 6就不会进入跳转表。
反之,会以L4(跳转表首地址)为偏置项,8为比例因子,x为偏移量进行跳转,跳到不同的代码段区域。
要注意的就是switch中一些特殊的情况,比如:
落空,就是case忘记带了break,那么会继续执行下一个case。
一个代码段对应多个标号,其实就是有几个case写了但都是空的,一步步递下来直到有代码段的地方。
目前没有很好的题……书上有例题到时候再看,主要先把作业搞完。

数组的访问

个人感觉这个比较容易的,不会过多讲,拿几个例子看一下。
如下图所示就是典型的数组的分配形式:在这里插入图片描述
所以我们只要知道首地址,加上我们想要访问的下标,乘以比例因子就可以访问到我们想要的元素了。
但上图也说明了,指针数据在我们IA32指令集是4Byte的,但x86-64就是8Byte的,因为指针就是地址呀,地址当然就是和机器的位数相关咯。

还有一点要提醒自己的就是指针取数,我们就是“寄存器中取到一块地址->地址映射到内存空间,去相应的内存里取数”,所以千万别忘了加括号哈。
这块内容其实只要C语言的基础扎实,前面讲过的指令全明白了应该问题不大。

然后就是什么嵌套数组,多维数组的访问。无非也是数据结构里“广义表”的内容吧,我们要时刻牢记的就是“行优先”吧。

可以看前面两篇的有几个题。

结构体

下面这张图应该我们映像里的结构体:
在这里插入图片描述
sizeof(int) = 4Byte × 3 = 12Byte分配给a数组。
4Byte分配给i。
指针分配4Byte。(说明不是64位的机器)
对于结构体内元素:
如果有一个结构体实例比如 rec r,可以直接用r.进行索引。
如果我们拿到的是结构体指针,那么就需要用“->”进行索引。
可以先看一个ppt里较复杂的例子:
在这里插入图片描述
%rdi保存rec指针首地址,%rsi就是val值。
我们会先索引到结构体里的i值,以此作为下标索引到数组位置进行赋值,然后进行下一个rec的操作。
然后就可以看我们的汇编代码:
%edx既然就是r,即首地址,其加上12就是索引到我们的i值,让%eax保存。
然后要把val数据传输到,以%eax里的值为下标的数组中去(此数组的首地址和r的首地址相同的,所以基址仍旧用%edx来写),最后把一个新地址给到edx。
同时,如若新地址为空,即0x0,就跳出循环。
有了这个例子,再看一道作业题:

5	第三章  结构体+函数+控制
已知node 结构体定义如下struct node{ long a;  struct node *next;}
请对以下init函数进行逆向分析,写出其C代码
Init:
movl 	$12,$eax
jmp		.TestExprStat
.Loop:
addq 	(%rdi),%rax
movq 	8(%rdi),%rdi
.TestExprStat:
testq 	%rdi,%rdi
jne		.Loop
ret

先设置%eax为12。
然后测试%rdi的值,可以猜测其为函数传入的一个参数。
只要这个不为空,就可以进入循环主体。
对于(%rdi),其就是一个取值操作,将这个值加到%rax中。
而后,又会将%rdi + 8取到的值给到rdi。
此时,要利用经验判断出,这个传入的参数就是指向node的一个指针。
那么对此指针首地址直接取值的操作实际上就是取到了long a。
而首地址加上8其实就是取到next指针,将这个指针取到的值覆盖原先的指针。
注意我们最后返回的是%rax,我们加指令是addq(4字,对应C语言中的long),况且为了不溢出还是得用long来设置ans。
很容易得到代码:

long Init(node* head){
    long ans = 12;
    while(head != NULL){
        ans += head->a;
        head = head->next;
    }
    return ans;
}

对齐

结构体还有一个知识点就是对齐的概念。
仍旧先看一张简单的图:
在这里插入图片描述
看起来这是一种浪费空间的行为,但实际上这会提高系统的性能。
在汇编中,使用.align关键字——例如“.align 4”表示4的倍数。

书上的解释也很通俗,如果把所有数据结构设置为8的倍数,那么我们可以通过一次读操作来完成全部读取。
否则的话,两个对象可能分别存储在2个8字节区域,需要多次读。

所以可以看这个题目:

6	第三章  结构体
struct{
char a;;
char *b;;
short c;
int d;
}
请问在紧凑布局和对齐布局中a/b/c/d字段的偏移量各是多少?

紧凑布局就太简单啦。
a 是0
b 是0 + sizeof(char) = 1
c 是1 + sizeof(char *) = 9
d 是9 + sizeof(short) = 11

对齐布局怎么办呢?
首地址还是无所谓所以,a偏移还是0。
但b不能偏移1,应该是8的一个倍数,所以会填充7Byte的空,使得b偏移为8。
8 + 8 = 16是2的倍数,所以short是直接符合的,因此short偏移为16。
最后16 + 2 = 18,并非4的倍数,所以要多填充2Byte,因此int的偏移为20。
同时我们发现20 + 4是24是最长的8的倍数,因此int后面无需再填充空位。
否则的话需要补充到最长的数据的整数倍,这样在结构体数组时才会更加对齐,否则简单的对齐会失效。
所以答案:

紧凑:
0 1 9 11
对齐:
0 8 16 20

对齐布局浪费空间终归算是一个缺点,那么怎么改善呢。
我们可以先放入占用空间大的数据,如下图所示:
(此题指定K=4因此我们必须每个数据块都占用4的倍数)
在这里插入图片描述

联合Union

C还有一种数据结构是Union,称作“联合体”。
联合体是按照内部最大元素分配空间。
同样的例子:在这里插入图片描述
会发现上面的Union只需要8Byte即可实现。
那么同样来看一道例题:

4	第三章  union+结构体
union a1{	
struct {  int * b1; char c1; long d1 } str1;
double data[3];
}
请问按照默认的对齐方式,上述a1占用多少字节空间?

首先要对struct进行分析。
int* b1占用8Byte,c1偏移8是1的整数倍,因此b1后面不填充。
那么对于d1其偏移就是8 + 1 =9,显然不是long 8Byte的整数倍,所以c1后面要填充7,即d1的偏移会是 8 + 1 + 7 = 16Byte。
那么最后一个struct的总量就是16 + 8 = 24,是8的倍数。
同理,一个3个double类型的数组占用空间为,3 × sizeof(double) = 24Byte。
取两者的大值,(两者相同)为24。
所以最后的a1仍旧是占用24Byte。

联合体常可以用来做数据转换,如这张图所示:
在这里插入图片描述
都是一开始拿一类型的4Byte数填充,然后返回的另一类型的数。
即相同的二进制编码进行不同数据类型的转换。
其实就是ppt的问号答案,就是相当于做了一个强制性的隐式转换。

ppt后面有50页也不像是书上的内容,一点小提示之类的,自己过一遍就得了。
就酱,然后今晚要结束ch03。
就是把栈帧结构和缓冲区溢出一起看。

以上是关于CSAPP-Revision-ch03的主要内容,如果未能解决你的问题,请参考以下文章

CSAPP-Revision-ch03

CSAPP-Revision-ch03

CSAPP-Revision-ch03

CSAPP-Revision-ch03

CSAPP-Revision-ch03

CSAPP-Revision-ch03