是否有兴趣
每当我们用c语言编写程序时,我们是否会对怎么从一个c语言的文本文件,转化为可执行文件代码与数据感兴趣。
如果你有兴趣的话,本文可以帮助你理解从c到汇编的规则。
机器架构
本文在32位Ubuntu操作系统上运行,并使用gcc编译器编译。编译命令为gcc -S xxx.c 命令结束后会在当前文件夹下生成一个xxx.s文件。这就是汇编文件。可以查看其中汇编代码。
正式开始了
c语言中可以定义各种类型的变量。可以对变量做任何算数操作,位操作。变量就是一块内存区域,机器指令可以操作该内存区域。加减乘除取余指令与或非异或左移右移指令。
定义变量
c语言可以定义基本数据类型,数组,异构体变量。在这里我们只讲基本数据类型,数组和异构体会在后文中体现。
基本数据类型变量有字符型char unsigned char一字节,短整形short unsigend short二字节,整形int unsigned int 4字节,长整形long unsigned long4字节。
定义
int fun()
{
char ch;
unsigned char uch;
short s;
unsigned short us;
int i;
unsigned int ui;
long l;
unsigned long ul;
}
汇编代码
pushl %ebp
movl %esp, %ebp
subl $32, %esp
leave
ret
pushl %ebp;movl %esp, %ebp;leave;ret。指令是用于构建函数使用的指令。我们可以暂时不看,以后会讲解。
subl $32, %esp向栈中申请了32字节。可以看出用户只申请了22个字节。但是C语言规定申请的用户空间为16字节的倍数,这是为了提高程序的运行效率。所以申请了32字节。32个字节中,-4(%ebp)为ul的内存地址,-8(%ebp)为l的地址,-12(%ebp)为ui的内存地址,-16(%ebp)为i的内存地址。-18(%ebp)为us的内存地址,-20(%ebp)为s的内存地址。-21(%ebp)为uch的内存地址,-22(%ebp)为ch的内存地址
赋值
int fun()
{
char ch;
unsigned char uch;
short s;
unsigned short us;
int i;
unsigned int ui;
long l;
unsigned long ul;
ch=1;
uch=2;
s=3;
us=4;
i=5;
ui=6;
l=7;
ul=8;
}
汇编代码
pushl %ebp
movl %esp, %ebp
subl $32, %esp
//ch=1
movb $1, -22(%ebp)
//uch=2
movb $2, -21(%ebp)
//s=3
movw $3, -20(%ebp)
//us=4
movw $4, -18(%ebp)
//i=5
movl $5, -16(%ebp)
//ui=6
movl $6, -12(%ebp)
//l=7
movl $7, -8(%ebp)
//ul=8
movl $8, -4(%ebp)
nop
leave
ret
其中使用指令movb,movw,movl。
movb S,D S->D 传送字节
movw S,D S->D 传送字
movl S,D S->D 传送双字
S可以是立即数,寄存器,内存地址。D可以是寄存器,内存地址。S与D不能同时为内存地址。也就是说一个内存地址(变量)赋值给另一个内存地址(变量)需要2步操作。从内存赋值到寄存器,将寄存器赋值给内存。
数据扩展
int fun()
{
char ch;
unsigned char uch;
short s;
unsigned short us;
int i;
unsigned int ui;
long l;
unsigned long ul;
i=ch;
i=uch;
}
汇编代码
pushl %ebp
movl %esp, %ebp
subl $32, %esp
movsbl -22(%ebp), %eax
movl %eax, -16(%ebp)
movzbl -21(%ebp), %eax
movl %eax, -16(%ebp)
nop
leave
ret
int fun()
{
char ch;
unsigned char uch;
short s;
unsigned short us;
int i;
unsigned int ui;
long l;
unsigned long ul;
i=s;
i=us;
}
汇编代码
pushl %ebp
movl %esp, %ebp
subl $32, %esp
movswl -20(%ebp), %eax
movl %eax, -16(%ebp)
movzwl -18(%ebp), %eax
movl %eax, -16(%ebp)
nop
leave
ret
其中movsbl指令做了符号扩展的字节传送到双字。movsbl S->D S可以是寄存器,内存地址。D可以是寄存器,内存地址。不能同时为内存地址。movswl指令做了符号扩展的字传送到双字。指令用法同movsbl。
movzbl 指令做了零扩展的字节传送到双字。
movzwl 指令做了零扩展的字传送到双字。
指令用法同于movsbl。
从上面代码我们可以看到汇编代码按照C语言的语法规则老老实实的进行了转化。
语法规则为
如果其中一个操作数的类型为long double,另一个操作数就会装换为long double类型。
否则,如果其中一个操作数的类型位double,另一个操作数就会转换为double类型。
否则,如果其中一个操作数的类型为float,另一个操作数就会装换为float类型。
否则,在两个操作数上所执行的是整型提升,其规则如下:
如果其中一个操作数的类型位unsigned long,另一个操作数就会转换为unsigned long类型。
否则,如果其中一个操作数的类型为long,并且另一个操作数的类型为unsigned,有两种可能性:
1.如果long可以表示unsignd类型的所有值,那么这个unsigned类型的操作数被转换为long类型。
2.如果long无法表示unsigned类型的所有值,那么这两个操作数都被转换为unsigned long类型。
否则,如果其中一个操作数的类型是long,另一个操作数就会转换为long类型。
否则,如果其中一个操作数是unsigned ,另一个操作数就会转换为unsigned类型。
否则,两个操作数都转换位int类型。
符号变量整形提升时做符号转换过,无符号变量整形提升时做无符号装换
数据截断
int fun()
{
char ch;
unsigned char uch;
short s;
unsigned short us;
int i;
unsigned int ui;
long l;
unsigned long ul;
ch=i;
uch=i;
}
汇编代码
pushl %ebp
movl %esp, %ebp
subl $32, %esp
movl -16(%ebp), %eax
movb %al, -22(%ebp)
movl -16(%ebp), %eax
movb %al, -21(%ebp)
nop
leave
ret
int fun()
{
char ch;
unsigned char uch;
short s;
unsigned short us;
int i;
unsigned int ui;
long l;
unsigned long ul;
s=i;
us=i;
}
pushl %ebp
movl %esp, %ebp
subl $32, %esp
movl -16(%ebp), %eax
movw %ax, -20(%ebp)
movl -16(%ebp), %eax
movw %ax, -18(%ebp)
nop
leave
ret