C语言基础知识汇编分析

来源:互联网 发布:虫哥耳机 知乎 编辑:程序博客网 时间:2024/06/05 07:20

       1.静态、动态存储变量的区别

C语言源码:

fun()

{

       int i = 0x100;

       static int c = 0x200;

       i = c;

}

汇编代码:

push       bp

mov bp, sp

push       si

mov si, 100h       ;定义动态变量 i = 0x100

mov si, ds:[0000] ;对静态变量赋值i = c;ds:[0000]内存储的是静态变量C

pop si

pop bp

retn

       总结:其实静态变量类似于全局变量,实现方式类似。


2.在C语言中将数组做为参数传递给函数
2009-08-04 08:42

C语言源码:

void change(int[]);

fun()

{

       int a[] = {1,2};

       add(a);

}

void change(int a[])

{

       int tmp;

       tmp=a[0];

       a[0]=a[1];

       a[1]=tmp;

}

汇编代码:

push       bp

mov bp, sp

sub sp, 4     ;开辟堆栈空间,保存数组数据

push       ss

lea   ax, [bp-4]

push       ax         ;es:di压栈

mov ax,0

push       ax

push       ds        ;ds:si压栈

mov cx,4       ;设置数组数据所占字节数,即需复制字节数

call sub_100C7

lea   ax, [bp-4]   ;此命令就是将数组的首地址传给调用的函数

push       ax         ;将数组的首地址压栈传递给子程序change

call sub_100C6

pop cx        ;平衡堆栈

mov sp, bp

pop bp

retn

sub_100C6:     ;change函数

push       bp

mov bp, sp

push       si

push       di        ;tmp局部变量

mov si, [bp+4]   ;将数组首地址读入si寄存器

mov di, [si]      ;si保存的是一个地址,这个地址中存储就是a[0],即指针 tmp = a[0]

mov ax, [si+2]   ;ax = a[1]

mov [si], ax     ;a[0]=ax a[0]=a[1] 通过ax寄存器中转

mov [si+2],di   ;a[1]=tmp

pop di

pop si

pop bp

retn

sub_100C7:     ;复制数据段数组数据至开辟的堆栈空间中            

push       bp

mov bp, sp

push       si

push       di

push       ds

lds   si, [bp+6]

les   di, [bp+0Ah]

cld

shr   cx, 1

rep movsw

adc cx, cx

rep movsb

pop ds

pop di

pop si

pop bp

retf 8

总结:传递数组其实就是传递数组的首地址,可以理解为传递的是数组指针,对数组的操作,将会影响数组中的数据。


3.语言的函数

这个的内容比较多,有点重量级。呵呵。。       

C语言源码:

int add(int,int);

fun()

{

       int a = 0x10;

       int b = 0x100;

       int c = add(a,b);

}

int add(int a,int b)

{

       int c;

       c = a + b;

       return c;

}

汇编程序代码:

push       bp

mov bp, sp     压栈bpbp寄存器将在后续部分作为基址寄存器使用

sub sp, 2      在堆栈中分配空间给变量C

push       si

push       di

mov si, 10h    变量a=0x10

mov di, 100h   变量b=0x100

push       di       

push       si        压栈ba做为参数传递给add函数

call sub_100B0

pop cx      

pop cx       平衡堆栈,pop cx机器指令较短,执行效率高,相当于add sp,4

mov [bp-2], ax 将返回值付给变量CC语言中通过ax寄存器返回函数值

pop di

pop si

mov sp, bp

pop bp

retn          至此函数fun返回

sub_100B0:

push       bp

mov bp, sp

push       si       

mov si, [bp+4] c = a

add si, [bp+6] c = a + b

mov ax, si     将结果传给ax寄存器

pop si

pop bp

retn        add子程序返回

总结:函数进入时基本上都有push bpmov bp,sp语句,此两条语句将bp寄存器压栈,过后做为基址寄存器使用。

调用函数时,先将参数从右到左依次压栈,执行call指令时,将ip压栈,接着将bp压栈,所以在16位的CPU中,取参数时,bp+4取得第一个参数,bp+6取得第二个参数,依次类推。局部变量使用使用sub sp,2指令在堆栈中开辟空间存储,所以取局部变量时,通过bp-2取得第一个局部变量,bp-4取得第二个局部变量,函数若有返回值一般通过ax寄存器返回。最后要执行mov sp,bppop bp指令平衡局部变量占用的堆栈空间,实际的参数并未清空。Ret后弹出ip值,返回调用程序执行的地址。

一般情况下由调用函数的程序平衡堆栈,所以上例pop cx执行两次,相当于add sp,4平衡堆栈。但数组中传送字符串的函数使用了ret n,由子程序本身进行了平衡堆栈的工作。

从上可以看到,在调用函数时,调用参数并未在子函数中更改,即在子函数中使用了参数值,但并未真正更改参数的值。使用指针做为参数时的情况,后续再研究。



4.数组的定义使用

C语言程序:

int x[] = {1,2};

int i = x[0]+x[1];

汇编程序代码:

Push bp

mov bp, sp

sub sp, 6          用到三个局部变量,其中数组2个,所以分配6个字节的堆栈空间

push       ss

lea   ax, [bp-6]

push ax

push       ds

mov ax, 194h

push       ax        以上压栈操作给子程序传递参数

mov cx, 4         数组占用字节长度

call SCOPY@ 注意:此处为远调用,压入CSIP寄存器

mov ax, [bp-6] ax寄存器内容x[1]

add ax, [bp-4]    x[0]+x[1]

mov [bp-2], ax    将结果付给[bp-2],即变量i

mov sp, bp

pop bp

retn                    以上语句为返回子程序

子程序SCOPY@

push bp

mov bp, sp

push si

push di

push ds               保存子程序中用到的寄存器

lds   si, [bp+6]    ds:si为调用程序中的DSAx的值,初始化字符串复制源地址

les   di, [bp+0Ah]es:di为堆栈空间的地址

cld

shr   cx, 1

rep movsw           将数据段中定义的数组内容复制到堆栈中分配的空间

adc cx, cx

rep movsb            此处有疑问,不太清楚。

pop ds

pop di

pop si

pop bp

retf 8                  堆栈平衡,清空子程序调用前压入的参数。

总结:C语言处理数组是通过在数据段定义数组内容,并且一般以16字节为单位,若不够则用“0”补齐,然后将DS及数组偏移地址,SS及堆栈开始地址压入堆栈供子程序调用,子程序负责将数据段中定义的数组内容复制到堆栈中。

一定要注意复制数组内容的子程序为远调用,曾经在此处分析时,出现问题。

多维数组其实就是按行存放,先放第一行的元素,再存放第二行的元素。

例:int i[2][2]内存中为:i[0][0]i[0][1]i[1][0] i[1][1]




5.C语言的各种循环语句

1、While循环

C语言程序:

int x = 1;

while(x<10)

{

       x++;

}

对应的汇编语句:

push    bp

mov    bp, sp

push    si

mov    si, 1

jmp    short loc_1009C

loc_1009B: inc si

loc_1009C:    

cmp    si,0Ah               判断x是否大于10

jl       short loc_1009B 小于则跳至inc six++

pop   si

pop   bp

retn

理解:先判断while语句后的条件,若是真,则执行后续语句,若为假则退出。

2、for循环

C语言程序:

int x = 0;

int i;

for(i=0;i<10;i++)

{

       x++;       

}

对应的汇编语句:

push    bp

mov    bp, sp

push    si

push    di

xor     di,di             设置x=0

xor     si,si              设置i=0

jmp     short loc_1009F

loc_1009D:

inc     di

inc     si

loc_1009F:

cmp    si,0Ah            比较i是否大于10

jl      short loc_1009D

pop   di

pop   si

pop   bp

retn

理解:先判断条件,若条件为真,则执行i++,执行下列语句;若条件为假,则退出循环。

3、do while循环

C语言程序:

int x = 1;

do

{

       x++;

}

while (x<10);

对应的汇编语句:

push    bp

mov    bp, sp

push    si

mov    si,1

loc_10099:

inc     si

cmp    si,0Ah

jl       short loc_10099

pop   si

pop   bp

retn

理解:先执行后续语句,后判断条件。换句话说,后续语句最少可以执行一次。

4、breakcontinue语句

理解:break语句用于switch或循环中,退出循环;continue语句用于结束此次循环

6.C语言Switch语句

       Switch语句

C语言程序:

int x = 1;

int y = 10;

switch(x)

{

       case 1: y = 0x100;

       case 2: y = 0x101;

       case 3: y = 0x102;

       default: y = 0x110;

}

汇编程序:

push    bp

mov    bp, sp

push    si

push    di

mov     di, 1

mov     si, 0Ah

mov     ax,1             di(即变量x)赋给ax

cmp     ax,1             case 1x1比较

jz       short loc_100B0   y=0x100

cmp     ax,2            case 2x2比较

jz       short loc_100B3   y=0x101

cmp     ax,3             case 3x3比较

jz       short loc_100B6   y=0x102

jmp     short loc_100B9 y=0x110

loc_100B0: mov si,100h

loc_100B3: mov si,101h

loc_100B6: mov si,102h

loc_100B9:

mov    si,110h

pop     di

pop     si

pop     bp

retn

总结:switch语句将会转变为类似多个if语句的结构。以下为上面的switch语句对应的C语言if语句描述,若不加break语句,则每条if语句顺序执行。

If(x==1) y=0x100;

If(x==2) y=0x101;

If(x==3) y=0x102;

Y=0x103


7.C语言If语句

       If语句

C语言程序:

int x = 1;

int y = 10;

if( x > y)       

       x=0x10;

else

{

       if(x==y)        

              y = 0x100;

       else

              y =0x102;

}

汇编程序:

push    bp

mov    bp, sp

push    si

push    di

mov     di, 1

mov     si, 0Ah

cmp     di, si

jle       short loc_100A6 x<=y则跳转至loc_100A6进入else部分

mov     di, 10h          x=0x10

jmp     short loc_100B2

loc_100A6:                           

cmp     di, si

jnz     short loc_100AF

mov     si, 100h

jmp     short loc_100B2

loc_100AF:

mov     si, 102h

loc_100B2:

pop     di

pop     si

pop     bp

retn



8.C语言逻辑运算符

一、逻辑运算符:

1、与运算符的使用

C语言程序:

int x = 1;

int y = 10;

if( x && y)

       x = 0x10;

else

       y = 0x100;

对应的汇编语句:

push    bp

mov    bp, sp

push    si

push    di

mov    si, 1

mov    di, 0Ah

or      si, si            求或运算,若为零则跳转至y=0x100

jz      short loc_100AA

or      di, di           求或运算,若为零则跳转至y=0x100

jz      short loc_100AA

mov    si, 10h

jmp    short loc_100AD

loc_100AA:

mov     di, 100h

loc_100AD:

pop     di

pop     si

pop     bp

retn

注意:与运算时,只要有一个为0则结果就为0,所以判断x是否为0,为0则跳转至y=0x100;若不为零,再判断y是否为0,若为零则亦跳转至y=0x100

2、或运算符的使用

C语言程序:

int x = 1;

int y = 10;

if( x || y)

       x = 0x10;

else

       y = 0x100;

对应的汇编语句:

push    bp

mov    bp, sp

push    si

push    di

mov    si, 1

mov    di, 0Ah

or      si, si

jnz     short loc_100A5   求或运算,若不为零则跳转至x=0x10

or      di, di

jz      short loc_100AA   求或运算,若为零则跳转至y=0x100

mov    si, 10h

jmp    short loc_100AD

loc_100A5:

mov     si, 10h

jmp     short loc_100AD

loc_100AA:

mov     di, 100h

loc_100AD:

pop    di

pop     si

pop     bp

retn

注意:求或运算时,先判断x是否为0,不为0则直接x=0x100;若为0,则再判断y是否为0,为0则直接y=0x100

3、非运算符的使用

C语言程序:

int x = 1;

int y = 10;

if(!y)

       x = 0x10;

else

       y = 0x100;

对应的汇编语句:

push    bp

mov    bp, sp

push    si

push    di

mov    si, 1

mov    di, 0Ah

or     di, di

jnz     short loc_100A6 y不为零则执行y=0x100

mov    di, 10h          若为零则执行x=0x10

jmp     short loc_100A9

loc_100A6:

mov     si, 100h

loc_100A9:

pop     di

pop     si

pop     bp

retn

注意:求非运算时,先判断y是否为0,若不为0y后,结果为假,则直接y=0x100,不为0则直接x=0x100

        综上所述:可以看到TC编译器,处理逻辑运算符时,先判断左边第一个参数,后判断接下来的参数,所以在使用时应该将最有可能出现的情况放在左边做判断,效率会更高一些。

8.C语言的关系运算符

1、大于号的使用

C语言程序:

int x = 1;

int y = 10;

if( x > y)

       x = 0x10;

else

       y = 0x100;

对应的汇编语句:

push    bp

mov    bp, sp

push    si

push    di

mov    si, 1

mov     di, 0Ah            x=1;y=10xy赋值

cmp     si, di                比较xy

jle     short loc_100A6 小于或等于转移至y=0x100

mov     si,10h

jmp     short loc_100A9 跳至返回部分代码

loc_100A6:

mov     di, 100h

loc_100A9:

pop     di

pop     si

pop     bp

retn

2、大于等于号的使用

C语言程序:

int x = 1;

int y = 10;

if( x >= y)

       x = 0x10;

else

       y = 0x100;

汇编关键部分:

cmp     si, di

jl      short loc_100A6 小于转移至y=0x100

mov    si, 10h

jmp     short loc_100A9

loc_100A6:

mov     di, 100h

loc_100A9:

pop     di

pop     si

pop     bp

retn

3、小于号的使用

C语言部分

if( x < y)

       x = 0x10;

else

       y = 0x100;

汇编关键部分:

cmp     si, di

jge      short loc_100A6 大于或等于转移至y=0x100

mov     si, 10h

jmp     short loc_100A9

loc_100A6:

mov     di, 100h

loc_100A9:

pop     di

pop     si

pop     bp

retn

4、小于等于的使用

C语言部分

if( x <= y)

       x = 0x10;

else

       y = 0x100;

汇编关键部分:

cmp     si, di

jg      short loc_100A6 大于转移至y=0x100

mov     si, 10h

jmp     short loc_100A9

loc_100A6:

mov     di, 100h

loc_100A9:

pop     di

pop     si

pop     bp

retn

5、 等于的使用

C语言部分

if( x == y)

       x = 0x10;

else

       y = 0x100;

汇编关键部分:

cmp     si, di

jnz     short loc_100A6 不等于则转移至y=0x100

mov     si, 10h

jmp     short loc_100A9

loc_100A6:

mov     di, 100h

loc_100A9:

pop     di

pop     si

pop     bp

retn

6、不等于的使用

C语言部分

if( x != y)

       x = 0x10;

else

       y = 0x100;

汇编关键部分:

cmp     si, di

jz     short loc_100A6 等于则转移至y=0x100

mov     si, 10h

jmp     short loc_100A9

loc_100A6:

mov     di, 100h

loc_100A9:

pop     di

pop     si

pop     bp

retn

小结:综上所述,TC编译器将关系运算符转变为相反的关系运算,保持了源程序的顺序,即相反的比较跳转至else部分,而正常的跳转至if后语句。


9.++、- -、+=的研究
2009-06-24 11:36

C语言程序:

int x = 1;

int y = 10;

int i = 0x99;

x++;

x = x + 1;

y--;

y = y - 1;

i += 1;

i += 2;

对应的汇编语句:

push    bp

mov    bp, sp

sub     sp, 2

push    si

push    di

mov    si, 1             变量x

mov    di, 0Ah         变量y

mov    [bp-2], 99h   变量i

inc     si                  x++

mov    ax, si

inc     ax

mov    si, ax           x = x + 1

dec     di                 y--

mov    ax, di

dec     ax

mov    di, ax            y = y - 1

inc     [bp-2]            i += 1

add     [bp+var_2], 2 i +=2

pop    di

pop    si

mov   sp, bp

pop     bp

retn

后续还有更深入一些的研究。


10.C语言的算术运算

C语言程序:

int a = 0x100;

int b = 0x200;

int c = 0x300;

int d = a * b + c + 'a';

对应的汇编语句:

push    bp

mov    bp, sp

sub     sp, 4

push    si

push    di

mov    si, 100h

mov    di, 200h

mov    [bp-4], 300h   变量C

mov    ax, si

mul    di                   a * b

add    ax, [bp-4]     a * b + c

add    ax, 61h ; 'a'   a * b + c + ‘a’

mov   [bp-2], ax      将计算结果赋给d变量

pop    di

pop    si

mov   sp, bp

pop   bp

retn

写的都比较简单,但是原理都是一样的。



11.C语言运算符的实现

C语言程序:

int a = 0x100;

int b = 0x200;

int c,d,e,f,g;   

c = a + b;

d = b - a;

e = a * b;

f = b / a;

g = b % a;

对应的汇编语句:

push    bp

mov    bp, sp

sub     sp, 0Ah    分配堆栈

push    si         保存函数中要用到的寄存器,C语言偏向使用si,di寄存器

push    di

mov    si, 100h

mov    di, 200h

mov    ax, si

add     ax, di

mov    [bp-8], ax   c = a + b

mov    ax, di

sub     ax, si

mov    [bp-6], ax d = b - a

mov    ax, si

mul    di

mov    [bp-4], ax e = a * b

mov   ax, di

cwd

idiv    si

mov    [bp-2], ax   f = b / a

mov    ax, di

cwd

idiv    si

mov    [bp-0Ah], dx g = b % a

pop     di

pop     si          恢复函数中使用的寄存器

mov    sp, bp        恢复堆栈指针

pop     bp

retn                返回程序pop ip

呵呵。。。写的比较简单,但是基本上反应了C语言编译器的意图。确实很佩服写TC2.0编译器的那些前辈,反汇编的代码精炼的很。C语言的执行效率就是通过简练的汇编代码保证的。



12.关于C语言局部变量的研究

前言:最近学习了汇编语言,想通过反汇编TC2.0的程序,了解C语言的各项元素到底是如何实现的。先做TC2.0的研究,DOS下的C语言研究透彻了。打好基础,学完Windows编程后,再对VC6的东西进行反汇编。

变量:C语言的局部变量一般定义在堆栈中,不同的类型占用不同的字节数。

以下定义了一个int整型变量:

push bp          将bp寄存器压栈,在后续程序中做为基址寄存器使用。

mov bp, sp

sub    sp, 2        在栈中分配空间存储局部变量

mov [bp-2], 100h 为局部变量赋值

mov sp, bp       恢复sp寄存器,将局部变量占用堆栈恢复,之前不能改变bp寄存器内容,否则程序无法正常返回

pop bp          

retn                   弹出ip寄存器内容,函数返回

a)         整型变量:long 之外其他的整型,均分配16位,占用两个字节;long整型占用32位,占用四个字节,一般会放在ax,dx寄存器中。

b)        浮点型变量:比较复杂,暂未分析。

c)        字符变量:使用8位(即一个字节)存储字符,但C语言编译器在栈中分配2个字节的存储空间(或许是因为分配两个字节效率高)。


指针的定义及存储方式

C语言源码:

f()

{

       int i = 0x100;

       int *p;

     p= &i;

}

汇编代码:

enter       4, 0         ;分配四个字节空间存储局部变量

mov [bp-4], 100h ;int i = 0x100

lea   ax, [bp-4]    ;将变量i的地址赋给ax

mov [bp-2], ax    ;int *p=&i

leave

retn

总结:指针即地址,指针变量存储的是i的地址。

C语言源码:

f()

{

       int a = 0x100;

       int b = 0x200;

       int *p,*p1,*p2;

       p1 = &a;

       p2 = &b;

       p = p1;

       p1 = p2;

       p2 = p;

}

汇编代码:

enter       6, 0

push       si

push       di

mov [bp-6], 100h   ;变量a int a = 0x100;

mov [bp-4], 200h ;变量b int b = 0x200;

lea   si, [bp-6]     ;变量p1 int *p1 = &a,现si中存储的是变量a的地址

lea   di, [bp-4]     ;变量p2 int *p2 = &b,现di中存储的是变量b的地址

mov [bp-2], si     ;p = p1

mov si, di        ;p1 = p2

mov di, [bp-2]    ;p2 = p1

pop di

pop si

leave

retn

       总结:lea命令是取地址的操作命令。



13.C语言的数组和指针的关系及++操作符的使用

C语言代码:

fun()

{

       int i[] = {'a','b'};    

       int *p = i;

       int i1= *++p;         

}

汇编代码:

enter 6,0                ;开辟空间存储数组元素

push si                 

push ss

lea ax,[bp-6]

push ax

push ds

mov ax,0

push ax

mov cx,4

call 000A:0029       ;以上为数组元素复制函数准备参数

lea si,[bp-6]           ;将数组首地址赋给si。即int p* = i

inc si

inc si                     ;以上两句完成++p

mov ax,[si]           

mov [bp-2],ax        ;以上两句完成int i1= *++p;

pop si

leave

ret

C语言代码:

fun()

{

       char c[] = {'a','b'};

       char *p = c;

       char c1= *p++;            

}

汇编代码:

enter 4,0                ;开辟空间存储数组元素

push si                 

push ss

lea ax,[bp-4]

push ax

push ds

mov ax,0

push ax

mov cx,2

call 000A:0029       ;以上为数组元素复制函数准备参数

lea si,[bp-4]           ;将数组首地址赋给si。即char p* = c

mov al,[si]             ;[bp-3]中存放字符串'b'[bp-2]未使用

mov [bp-1],al         ;以上两句完成char c1= *p

inc si                     ;p++

pop si

leave

ret

总结:以上均省略了数组元素复制的代码。通过以上代码可以发现C语言编译器可以根据定义的指针的类型,对++操作进行指定的字节后移,char类型占用一个字节,则p++对应为inc siint类型占用两个字节,则p++对应为inc si两次。

指针多使用lea命令将地址赋值给相应的指针变量。另外可以看到p++++p的差别,p++是先操作后加,++p为先加后操作



14.C语言的函数指针和指向函数的指针变量

C语言代码:

main()

{

       int max();

       int (*p)();

       int a,b,c;

       a = 0x10;

       b = 0x20;

       p = max;

       c = (*p)(a,b);

}

max(int a,int b)

{

       int z;

       if(a > b) z = a;

       else

              z = b;

       return b;

}

汇编代码:

push       bp

mov bp, sp

sub sp, 4                     ;分配变量cb。其中c-[bp-2]b-[bp-4]

push       si

push       di

mov di, 10h                    ;变量a = 0x10

mov [bp-4], 20h            ;变量b = 0x20

mov si, 21Eh                ;p = max即函数的地址,此处21Eh为函数的地址

push       [bp-4]

push       di

call si ; sub_1021E       ;压栈ba跳转至si指向的函数的地址

pop cx

pop cx                        ;堆栈平衡

mov [bp-2], ax             ;c = (*p)(a,b),一般函数通过ax返回值。

pop di

pop si

mov sp, bp

pop bp

retn

sub_1021E:                   ;max函数开始,地址为21Eh

push       bp

mov bp, sp

push       si

push       di

mov si, [bp+6]              ;变量b

mov ax, [bp+4]             ;变量a

cmp ax, si                     ;if语句开始

jle    short loc_10232     ;a <= b z = b,否则 z = a

mov di, [bp+4]             ;z = a

jmp short loc_10234

loc_10232:                          

mov di, si                     ;z = b

loc_10234:                          

mov ax, si                    ;将返回值赋给ax寄存器

pop di

pop si

pop bp

retn

总结:函数的指针其实就是函数的开始地址。注意函数的参数必须一致,否则在调用实际的函数时,就会出现问题。

另外:C语言编译器发现局部变量较少时,使用disi寄存器存储局部变量,这样可以提高程序的运行速度。



原创粉丝点击