tags
type
status
date
slug
summary
category
password
icon
#2 Bits-Bytes-Ints
#Bit-wise Operation
C语言中,所有“整型”都可以进行按位运算,运算符包括:
& | ^ ~
。在进行这类运算的时候,把数看成一个”bit的向量“,而不管其数学含义注意以上的四种运算符与
&& || !
不同!后者的结果只能是0x00和0x01#Shift Operations
左移直接在右边补0。
右移分为两种:逻辑右移无论如何都在左边补0,算术右移在左边的补位与移动前最高位相同(这样无论对正数还是负数,都相当于在补0)
#Integers
无符号数Unsigned Int:没有负数
有符号数Signed Int:补码最高位的权重为。
在代码中类型转化的时候,内存里的二进制数是不变的,改变的只是我们如何解读它们
如在C中,unsigned与signed比较时,大家会一起转成unsigned,于是会出现-1>0U的情况
extension
左边补符号位,这样不改变所代表的数值
trunction
直接去掉最高位,有信息损失
#Word Size & Byte Ordering
计算机中以字节Byte为单位。
其基本整数的比特数称为字长word size
显然,一个word包含多个Byte,计算机如何定义多个字节的高低位顺序也可能不同
从运算的角度,应该把低位放在前面,这样从前到后处理,符合运算顺序,这也是x86、ARM在用的Little Endian
然而互联网上采用Big Endian,从高位向低位传输字节。于是就需要网络驱动程序进行转换。
#3 Float
每个浮点数分为s+exp+frac三部分,
单精度三部分分别是1位、8位、23位;双精度分别是1位、11位、52
#符号位s
0表示正数,1表示负数。会出现+0和-0两个数
#Normalized
判据:当exp不是全0或全1
exp不是按照补码的方式解读的,而是按照无符号数算,再减去bias
,
此时M为1.frac,所以
#Denormalized
判据:exp全0
(而不是),与normalized的最大负指数相同
此时M为0.frac
这类数用于处理很靠近0的数
#Special
判据:exp全1
若frac全0,则为inf
若frac不全为0,则为nan
#Comparison
基本(除了inf和nan)可以把整个浮点数当做无符号整数来比较大小
#4-6 Machine Programming
Architecture(also ISA:instruction set architecture)规定指令要怎么写。典型的有:x86(电脑)、ARM(手机)、RISC V(开源)
机器语言Machine Code;汇编语言Assembly Code
Microarchitecture:如何在硬件层面实现这些指令
CISC(指令种类多,硬件功能多,代码密度高,如x86);RISC(指令种类少,硬件功能少,复杂指令用多条简单指令实现,如)
x86架构下的CPU型号(按时间顺序):8086、80286、386、486、奔腾Pentium、酷睿Core
Intel一度想利用自己的市场霸权地位推动64位架构(与原32位x86不兼容),但效果很差。而后IBM推出与原32位架构兼容的x86-64架构,抢夺Intel市场份额。随后Intel也推出了与其几乎一样的64位架构。
冯诺依曼结构一大特点:储存程序。命令写在内存里,“翻书”的动作写在书上
Register file:寄存器集群
PC:下一条命令的地址
Condition Codes:存储最近一次运算的一些Conditions
#x86-64 Integer Registers
从两字节的寄存器一步步扩展而来,ax(ah+al)→eax→rax,所以名字看起来有些奇怪
rsp是用于栈操作的寄存器,不能乱用
#Operations
movq Source, Dest
移动数据数据包括Immediate(可以理解为常数,
$0x4
)、寄存器中的数(%rax
)、内存中处于寄存器给出的地址处的数(最简单的是(%rdx)
,还有更复杂的表达如0x80(,%rdx,2)
)leaq Src, Dst
不访问内存而将算出的地址存于寄存器中#Condition Codes
Implicily set隐式设置
结果全0时,ZF置1
有符号数运算结果为负数时,SF置1
无符号数溢出or从溢出位借位时,CF置1
有符号数溢出时,OF置1
Explicitly set显式设置
cmpq b,a
计算a-b,但不存到什么地方,只更新Condition Codestestq b,a
计算a&b,同上setx %xxx
根据Condition Codes设置某寄存器的1个字节(不会改变其他字节,所以经常用movzbl之类的指令将该寄存器的其他字节清零)jx xxx
根据Condition Codes跳转Conditional Branch
Conditional Moves
在前一种方法中,.L4前的代码能不能执行,要等到.L4才行,这样不好
我们可以把两种可能的运算都先做出来,最后再判断采用哪个值作为ret
但是这样也会有问题:
#Procedures调用
要解决的问题:
- 控制转移。如何跳转到调用函数的开头?如何跳回到调用语句?
- 数据传递。如何把参数传入调用函数?如何处理返回值?
- 内存管理。局部变量怎么放?在返回时怎么删除?
⇒Application Binary Interface(ABI)
Stack
因为程序语句存在内存里地址较小的地方,所以栈从地址较大的地方递减存储。即:栈底高位,栈顶低位。
%rsp储存栈顶的地址
Passing Control
%rip储存下一条要执行的命令的地址。跳转的命令实际上只是修改了rip。每个时针的上升沿都执行一次命令
pushq Src
将地址为Src处的指令压进栈中(%rsp-8)popq Dest
将栈顶弹出,并存入Dest(%rsp+8)call label
将return address(即返回后的下一条命令的地址)压入栈中,然后跳转到地址为label处的命令ret
弹出栈顶的地址,存入rip,即下一条执行return address处的命令例.
- %rip=0x400544,故执行
callq 400550
。因此把400549(return address)压入栈顶,%rsp变为0x118【16进制,与0x120差了8】,同时%rip变为400550
- %rip=0x400550,故执行
mov %rdi,%rax
。跳过中间一些命令,最后%rip变为400557
- %rip=0x400557,故执行
retq
。取出栈顶地址并赋值给%rip,%rip变为400549,%rsp变为0x120
- %rip=400549,故执行
mov
。后续略
Passing Data
返回值放在%rax中
前6个传入的参数放在6个寄存器中,更多的参数放在栈里
Stack Frames
在调用函数的时候,栈里面不仅要存return address,还要存超过6个的参数,以及一些local variables,所以这就需要提前分配栈空间。每个函数都有自己的Stack Frame
可以使用%rbp存上一个Frame的栈顶,方便恢复
寄存器的使用:caller saved即caller先存好,callee随便改;callee saved即如果callee要用,用完必须要恢复原状态
- 作者:XiaoTianyao
- 链接:https://www.xty27.top/article/ics
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。