学好二进制必须先打好基础。
了解了电脑中的程序如何运行,以及栈在程序运行时是如何变化的。
程序运行基本原理
CPU 访问快慢的速度依次为:寄存器->缓存->内存->硬盘。
硬盘用于永久存储所有的数据。当程序运行时,程序内容会被放进内存中,占用内存的空间。缓存和寄存器则相对速度更快,作为数据和 CPU 之间连接的桥梁。
寄存器
CPU 内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。和 CPU 速度相当,空间比较小在 kb 级别。CPU 访问寄存器的速度是最快的。寄存器是一种容量有限的存储器,并且非常小,因此只把一些计算机的指令等一些计算机频繁用到的数据存储在其中,加快直接同内存读取指令和读写数据的速度。
缓存(CACHE)
存在于内存与 CPU 之间的存储器,容量比较小但速度比内存高得多,接近于 CPU 的速度,比寄存器要慢 1 倍左右,但是空间可以达到 MB 级别。高速缓存在 CPU 要频繁访问内存中的一些数据时,如果每次都从内存中去读,花费的时间会更多,因此在寄存器和内存之间有了缓存,把 CPU 要频繁访问的一些数据存储在缓冲中,这样效率就会更高。但需要注意的是,缓冲的大小也是很小的,不能存放大量的数据。缓存又可以分为一级和二级缓存,一级的速度大于二级的速度。CPU 向内存读取数据时,首先查询缓存区是否有对应数据,如果有则直接读取,没有再从内存中读取。
内存
计算机运行过程中的存储主力,用于存储 指令(编译好的代码段),运行中的各个静态,动态,临时变量,外部文件的指针等等。寄存器和高速缓存只是加速存储速度的中间部件,原始运行文件都是先加入到内存中,因此内存的大小决定了一个可运行程序的最大大小。速度比缓存要慢 10 倍左右,但是空间可以达到 GB 级别,当前个人电脑一般都不小于 4G。分为只读(ROM)和随机存储器(RAM)。
硬盘
用来存储需要永久存储的文件,归入外存储器,访问速度比内存要慢上万倍,但是价格也比较便宜,空间也很大。
ASCII 码
#include<stdio.h>
int main()
{
printf("%d\n", 'A');
printf("%c\n", 65);
}
小端序
数据的高位字节存放在地址的高端,低位字节存放在地址的低端
不同类型数据的字节长度
- sizeof(int): 4 bit
- sizeof(short): 2 bit
- sizeof(float): 4 bit
- sizeof(double): 8 bit
- sizeof(char): 1 bit
不同类型数据的小端序存储方式
- int 0x12345678: 78 56 34 12
- char [] “1234”: 30 31 32 33 00
- short 0xdead: ad de
- short 0x12345678: 34 12 78 56
栈
- 先进后出
- 从高地址向低地址延伸
作用:
- 暂时保存变量
- 调用函数时传递参数
- 保存函数返回地址
调试 SWAP 程序
#include<stdio.h>
void swap(int *aa, int *bb)
{
int cc = *aa;
*aa = *bb;
*bb = cc;
}
int main()
{
int a = 100, b = 2;
swap(&a, &b);
printf("%d %d\n", a, b);
return 0;
}
表格模拟程序执行时的栈:
ADDRESS | VALUE | NAME |
---|---|---|
0x7ffee7574978 | 2 | b |
0x7ffee7574974 | 100 | a |
0x7ffee7574970 | 0x7ffee7574978 | bb |
0x7ffee757496c | 0x7ffee7574974 | aa |
0x7ffee7574968 | ?? | cc |
流程图模拟程序执行时的栈:
以下为在 mac 下反汇编出的代码,基本和 linux 下的一致,但是和 windows 下的有一定区别:
0x10958ff10 <+0>: pushq %rbp
0x10958ff11 <+1>: movq %rsp, %rbp
0x10958ff14 <+4>: movq %rdi, -0x8(%rbp)
0x10958ff18 <+8>: movq %rsi, -0x10(%rbp)
- 0x10958ff1c <+12>: movq -0x8(%rbp), %rsi
0x10958ff20 <+16>: movl (%rsi), %eax
0x10958ff22 <+18>: movl %eax, -0x14(%rbp)
0x10958ff25 <+21>: movq -0x10(%rbp), %rsi
0x10958ff29 <+25>: movl (%rsi), %eax
0x10958ff2b <+27>: movq -0x8(%rbp), %rsi
0x10958ff2f <+31>: movl %eax, (%rsi)
0x10958ff31 <+33>: movl -0x14(%rbp), %eax
0x10958ff34 <+36>: movq -0x10(%rbp), %rsi
0x10958ff38 <+40>: movl %eax, (%rsi)
0x10958ff3a <+42>: popq %rbp
0x10958ff3b <+43>: retq
Linux 下采用的是 AT&T 的汇编语法格式,Windows 下面采用的是 Intel 汇编语法格式。二者的主要区别在于:
指令操作数的赋值方向是不同的
Intel:第一个是目的操作数,第二个是源操作数
AT&T:第一个是源操作数,第二个是目的操作数指令前缀
AT&T:寄存器前边要加上%
,立即数前要加上$
Intel:没有这方面的要求内存单元操作数
Intel:基地址使用[]
AT&T:基地址使用()
比如:intel 中mov ax, [bx]
;AT&T 中movl (%eax), %ebx
操作码的后缀
AT&T 中操作码后面有一个后缀字母:l
32 位,w
16 位,b
8 位
Intel 却使用了在操作数前面加dword ptr
,word ptr
,byte ptr
的格式
例如:mov al, bl
(Intel);movb %bl, %al
(AT&T)AT&T 中跳转指令标号后的后缀表示跳转方向,
f
表示向前,b
表示向后
参考网站
https://blog.csdn.net/sinat_37138973/article/details/79012270
https://blog.csdn.net/younkerjqb/article/details/53432422
https://blog.csdn.net/chuchus/article/details/38469403