程序的机器级表示
程序编码
1.C预处理器扩展源代码,插入所有用#include命令指定的文件,并扩展所有用#define声明指定的宏
2.编译器产生源文件的汇编代码 .s文件
3.汇编器将汇编代码转化为二进制目标代码文件 .o文件
4.连接器将两个目标代码文件与实现库函数的代码合并,产生最终的可执行代码
机器级代码
名称 | 程序计数器(PC) |
---|---|
x86-64表示 | %rip |
含义 | 给出将要执行的下一条指令在内存中的地址 |
要查看机器代码文件的内容,可以用到反汇编器(disassembler)
反汇编器只是基于机器代码文件中的字节序列来确定汇编代码。
linux> objdump -d mstore.o
数据格式
C声明 | Intel数据类型 | 汇编代码后缀 | 大小(字节) |
---|---|---|---|
char | 字节 | b | 1 |
short | 字 | w | 2 |
int | 双字 | l | 4 |
long | 四字 | q | 8 |
char* | 四字 | q | 8 |
float | 单精度 | s | 4 |
double | 双精度 | l | 8 |
访问信息
一个x86-64的CPU包含一组16个存储64位值的通用目的寄存器,用来存储整数数据和指针。
源操作数分为:
1.立即数
2.寄存器
3.内存引用
指针的一些示例
C语言中的“指针”其实就是地址。
间接引用指针就是将该指针放在一个寄存器中,然后在内存引用中使用这个寄存器
- C操作符 * 执行指针的间接引用
long x = *xp // pointer dereferencing
将读存储在xp所指位置的值,并将它放在名字为x的局部变量中
- C操作符 & 称为取址操作符,创建一个指针。
long a = 4;
long b = exchange(&a, 3);
printf("a = %ld, b = %ld\n", a, b)
这段代码打印出
a = 3 , b = 4
该指针指向保存局部变量a的位置,然后函数exchange()将用3覆盖存储在a中的值,但是返回原来的值4作为函数的值。
压入和弹出栈数据
栈在处理过程调用中起到至关重要的作用。
栈是一种数据结构,可以添加或者删除值,不过要遵循“后进先出”的原则。
通过push将数据压入栈中,通过pop操作删除数据。
指针%rsp保存着栈顶元素的地址,x86-64中栈是向下增长的,所
以压栈是减小指针的值,出栈是增大指针的值。
加载有效地址
加载有效地址(load effective address)指令leaq实际上是movq指令的变形。
它的指令形式是从内存读数据到寄存器,但实际上它并没有引用内存。
leaq S, D // D <- &S
它的第一个操作数看上去是一个内存引用,但是该指令并不是从指定的位置读入数据,而是将有效地址写入目的操作数。这条指令可以为后面的内存引用产生指针。