1,GCC C语言编译器以汇编代码的形式产生输出,汇编代码是机器代码的文本表示,给出程序中的每一条指令。然后GCC调用汇编器和链接器,从而根据汇编代码生成可执行的机器代码。
2,用高级语言编写的程序可以在很多不同的机器上编译和执行,而汇编代码则是与特定机器密切相关的。
3, 超线程:一个处理器上可以同时运行两个程序。多核:将多个处理器实现在一个芯片上。
4,C语言中所谓的“指针”其实就是地址。间接引用指针就是将该指针放在一个寄存器中,然后在存储器引用中使用这个寄存器。其次,局部变量通常是保存在寄存器中,而不是存储器中。寄存器的访问比存储器的访问要快得多。
5,移位操作:先给出移位量,然后第二项给出的是要移位的位数。它可以进行算数和逻辑右移。移位量用单个字节编码,只允许进行0到31位的移位。移位量可以是一个立即数,或者放在单字节寄存器元素%cl中。移位操作的目的操纵数可以是一个寄存器或是一个存储器位置。
6,左移指令有两个名字:SAL和SHL,两者的效果是一样的,都是将右边上0。右移指令不同,SAR执行算术移位,而SHR执行逻辑移位。
7,假如我们用一条ADD指令完成等价于C表达式t=a+b的功能,这里变量a,b和t都是整型的。然后,根据下面的C表达式来设置条件码:
CF: (unsigned)t<(unsigned)a; 无符号溢出
ZF: (t==0); 零
SF: (t<0) 负数
OF: (a<0==b<0)&&(t<0!=a<0); 有符号溢出
8,CMP指令根据他们的两个操作数之差来设置条件码,除了只设置条件码而不更新目标寄存器之外,CMP指令与SUB指令的行为是一样的。TEST指令的行为与AND指令一样,除了他们只设置条件码而改变目的寄存器的值。testl %eax,%eax 用来检查%eax是负数,零还是整数。
9,条件码通常不会直接读取,常用的使用方法有三种:a,可以根据条件码的某个组合,将一个字节设置为0,或者1;b,可以条件跳转到程序的某个其他的部分;c,可以有条件的传送数据。
10,一条SET指令的目的操作数是8个单字节寄存器元素之一,或是存储一个字节的存储器位置,将这个字节设置成0或者1.
11,jmp指令是无条件跳转,它可以是直接跳转,即跳转目标是作为指令的一部分编程的;也可以是间接跳转,即跳转目标是从寄存器或存储器位置中读出的。
12,当执行与PC相关的寻址时,程序计数器的值是跳转指令后面的那条指令的地址,而不是跳转指令本身的地址。
13,汇编语言中,直接跳转是给出一个标号作为跳转目标的;间接跳转的的写法是“*”后面跟一个操作数指示符。例如:指令 jmp *%eax用寄存器%eax中的值作为跳转目标,而指令 jmp *(%eax)以%eax中的值作为读地址,从存储器中读出跳转目标。
14,数据的条件转移:这种方法先计算一个条件操作的两种结果,然后再根据条件是否满足从而选取一个。
15,IA32程序用程序栈来支持过程调用。机器用栈来传递过程参数,存储返回信息,保存寄存器用于以后回复,以及本地存储。为单个过程分配的那部分栈成为栈帧。栈帧的最顶端以两个指针界定,寄存器%ebp为帧指针,而寄存器%esp为栈指针。
16,栈向低地址方向增长,而栈指针%esp指向栈顶元素。将栈指针的值减小适当的值可以分配没有指定初始值的数据的空间,类似的,可以通过增加栈指针来释放空间。
17,用leave指令可以使栈做好返回的准备,它等价于下面的代码序列:
movl %ebp,%esp
popl %ebp
18,IA32中将程序计数器放到整数寄存器中的唯一方法:
call next
next:
popl %eax
19,GCC产生的代码有时候会使用leave指令来释放栈帧,而有时候会使用一个或者两个popl指令,两种方法都可行。
20,假设E是一个int型的数组,并且我们想计算E[i],在此,E的地址存放在寄存器%edx,而i存放在寄存器%ecx中,然后指令movl (%edx,%ecx,4) %eax会执行地址计算Xe+4i,读这个存储器的位置的值,并将结果存放到寄存器%eax中。允许的缩放因子1,2,4,8覆盖了所有基本简单数据类型的大小。
21,表达式Expr与*&Expr是等价的。数组引用A[i]等同于表达式*(A+i),它计算第i个数组元素的地址,然后访问这个存储器位置。
22,结构体变量所占内存长度是各成员占的内存长度之和,每个成员分别占有其自己的内存单元。共用体变量所占的内存长度等于最长的成员的长度。
23,一个联合的总的大小等于它最大字段的大小。
24,联合可以用来访问不同数据类型的位模式。例如:下面这段代码返回的是float作为unsigned的位表示:
unsigned float2bit(float f)
{
union{
float f;
unsigned u;
}temp;
tmep.f=f;
return temp.u;
}
在这段代码中我们以一种数据类型来存储联合中的参数,又以一种数据类型来访问他。
有趣的是,为此过程产生的代码与为下面这个过程产生的代码是一样的:
unsinged copy(unsigned u)
{
return u;
}
这就证明了机器代码中缺乏类型信息。无论参数是一个float还是一个unsigned,它都在相对%ebp偏移量为8的地方,过程只是简单的将它的参数复制到返回值,不修改任何位。
25,数据对齐:Linux沿用的对齐策略是,2字节数据类型的地址必须是2的倍数,而较大的数据类型的地址(例如:int ,int*,float和double)的地址必须是4的倍数。注意,这个要求就意味着一个short类型对象的地址(二进制)最低位必须等于0;类似的,任何int类型的对象或指针的地址(二进制)的最低两位必须都是0。
26,Microsoft Windows对齐要求:任何K字节基本对象的地址都必须是K的倍数。
27,对于结构体的代码,编译器可能需要在字段的分配中插入间隙,以保证每个结构元素都满足它的对齐要求。另外,编译器结构的末尾可能需要一些填充,这样结构数组中的每个元素都会满足它的对齐要求。
28,数组引用和指针运算都需要用对象大小对偏移量进行伸缩。当我们写表达式p+i,指针p的值为p,得到的地址计算为p+L*i,这里L是与p相关联的数据类型的大小。
29,将指针从一种类型强制转换为另一种类型,只改变他的类型,而不改变它的值。强制类型转换的一个效果是改变指针运算的伸缩。
30,函数指针的值是该函数机器代码表示中的第一条指令的地址。
阅读(2171) | 评论(0) | 转发(0) |