C_and_CPP 版 (精华区)

发信人: hua (pupa), 信区: C_and_CPP
标  题: 函数调用方法之一zz
发信站: 哈工大紫丁香 (Sat May 14 15:59:41 2005), 转信

http://www.csdn.net/develop/article/17/17411.shtm

C语言生成的代码在执行效率上比其它高级语言都高.现在让我们来看看C语言生成的代码
具体是什么样子的.当你看完本文对于C语言的了解一定会更深一步了.

本文通过一个个实际案例程序来讲解C语言.

研究案例一

工具: Turboc C v2.0,Debug,MASM v5.0,NASM
实例C程序:
/* example1.c */
char ch;
int e_main()
{

e_putchar(ch);

}

目标内容:C语言调用函数的方法与细节

我们使用的C编译器是16位的Turboc C v2.0,它生成的是16位的代码,比较简单,方便我们
来研究.同时我们也需要用到DOS下的DEBUG来进行反汇编.由于我们很多案例中的程序并
不是完整的C程序,所以Turboc下的Tlink并不能为我们生成目标程序,所以我将使用MASM
中的link.exe,同时里面的exe2bin.com也可以为我们把exe文件转换成bin文件.

这个程序没有main函数,我们用e_main来代替main函数.这样我们能避开C语言对main函数
进行一系列处理的代码.同样,我们也用e_putchar()来代替我们平常使用的putchar().这
里"e"的意思就是"example".

没有了main函数,我们的C程序就没有了入口,所以在开始编译这段C代码之前,我还得写几
行简单的汇编代码,通过它来作为我们程序的入口.

; C程序的入口 start.asm
[BITS 16]
[global start]
[extern _e_main]
start:
  call _e_main

按照C语言的习惯,所以C总的名词都要自动在前面加一个"_"下划线.所以,我们在C中的e_
main函数,如果要在汇编中调用,就变成了_e_main函数.这段汇编代码只有一句:call 
_e_main,就是调用我们在C中的e_main函数


这段代码我将用nasm来进行编译.生成start.obj
nasmw -f obj -o start.obj start.asm

下面我们用Turboc C来编译这段C代码:
TCC -mt -oexample1.obj -c example1.c
link start.obj example1.obj,example1.exe,,,
exe2bin example1.exe
这样,我们就得到了这段C代码编译出来的机器代码文件(example1.bin)了.
下面我们用DEBUG这个老DOS的工具来对example1.bin进行反汇编.

DEBUG
-n example1.bin
-l 0
-u 0
xxxx:0000   CALL 0003
xxxx:0003   MOV  AX,000B
xxxx:0006   PUSH AX
xxxx:0007   CALL 0020
xxxx:000A   POP  CX

这里看到蓝色的代码就是我们整个C程序的所生成的代码了.
最开始的第一句CALL 0003是我们用nasm编译的start.asm所生成的代码.
我们主要目标是研究蓝色的C语言的代码,第一句start.asm所生成的代码太简单,就是调
用e_main函数.而我们的e_main函数就是蓝色代码部分.

从C源程序中我们看到,我们在e_main做的就是一件事情:调用e_putchar(ch);其中ch是传
给出e_putchar的参数.

MOV AX,000B 
000B就是我们的全局变量ch所在内存的地址.C语言会把所有的全局变量在另一块内存区.
C代码先把ch的地址传给AX,然后通过
PUSH AX
把AX的值,也就是ch的地址压入堆栈.然后再
CALL 0020
而0020就是e_putchar代码的地址.通过这跳语句,计算机就跳到e_putchar的代码部分去
执行了.我在这里并不给出e_putchar的代码,因为我们这个案例只是研究C语言中如何传
递参数给其它函数的,并不管e_putchar如何取参数.下在一个案例中,我们将研究函数如
何取参数.

在这里我得把CALL指令解释清楚,因为在下个研究函数如何取参数的部分中大家可能会迷
惑.CALL XXXX 指令简单地或就是
PUSH IP
JMP XXXX
它首先把当前的执行地址IP压入堆栈,然后跳转到要CALL的地址去.CALL和RET指令是配套
的.RET指令等同于
POP IP
也就是回复CALL前的执行地址IP.
正因为这样,所以你一旦使用了CALL指令,你的堆栈指针SP就会自动减2.

POP CX
是每个函数调用完毕后都有的必备操作.在这里它不起任何作用.可能唯一的作用就是与C
ALL 0020前的PUSH AX像对应.这样堆栈指针SP才能回原.

好了,简单的第一个案例研究结束了.虽然就这4跳指令,但是我们已经可以看出C语言传递
参数方法了.总结起来就是
通过"MOV AX,参数地址"把参数的地址传到AX,然后"PUSH AX"把参数的地址压入堆栈.最
后"CALL 函数地址"转向执行要调用的函数.最后调用完后,"POP CX",恢复堆栈指针SP.

研究案例二

工具: Turboc C v2.0,Debug,MASM v5.0,NASM,TASM
实例C程序:
/* example1.c */
char ch;
extern void e_putchar(char c);
int e_main()
{

ch=0x44;
e_putchar(ch);

}
实例汇编程序:
; eio.asm
_TEXT segment byte public 'CODE'
DGROUP group _TEXT
  assume cs:_TEXT,ds:DGROUP,ss:DGROUP
  public _k_putchar
_k_putchar proc  near
 push  bp
 mov  bp,sp
 mov  ah,0eh
 mov  bx,7h
 mov  al,byte ptr [bp+4]
 int  10h
 pop  bp
 ret
_k_putchar endp

 

目标内容:C语言中函数使用参数的方法

这一节我们将使用TASM用汇编来写个标准的C函数.这一节的内容大家可能在很多汇编的
书籍上都看到过.讲的是C语言和汇编语言的连接方法.可能你会奇怪,我们这里已经有了M
ASM,NASM两个汇编编译器了,为什么还要使用TASM另外一个汇编编译器.我不知道MASM是
否可以和我们的Turboc C配合,但是TASM是肯定可以和Turboc C完全配合的.毕竟它们都
是Borland公司的产品,而且Turboc C中用-S生成的汇编代码是完全按照TASM中的语法而
定的.这足以见Turboc C和TASM之间"亲密的"关系了.

这个案例中我们主要并不研究C代码了.而是研究那个用汇编写的C函数.
 push  bp
 mov  bp,sp
 mov  ah,0eh
 mov  bx,7h
 mov  al,byte ptr [bp+4]
 int  10h
 pop  bp
 ret
其中byte ptr[bp+4]就是我们传给e_putchar()的参数值.
前一个案例中我们一直知道了C语言是把参数的地址压入堆栈的方式传给函数.所以在标
准的C函数中,都是通过取堆栈里的值来读参数.
标准的C函数前两行都是
 push  bp
 mov  bp,sp
首先保存BP的值,然后把当前的堆栈指针传递给BP,我们访问传递给该函数的参数就是通
过BP.而第一个参数值就放在BP+4的地址中,第二个参数值就放在BP+6,...,这样依此对应
每个参数的地址.BP就是CALL调用前的IP的值.因为CALL执行的时候,系统会自动把当前的
IP压入堆栈.关于这个前面一个案例中已经给出介绍.

别看这个C函数是用汇编语言写的,它可是个完完整整的C函数.
好了,让我们编译出来看看.

TASM eio.asm eio.obj
TCC -mt -oexample1.obj -c example1.c
link start.obj eio.obj example1.obj,example1.exe,,,
exe2bin example1.exe

好了,该是我们总结的时候了.
C语言中函数访问参数的方法就是先通过"PUSH BP"保存BP,"MOV BP,SP"把当前的堆栈指
针传递给BP.第一个参数的地址就在BP+4,第二个参数的地址就在BP+6,...比如"MOV 
AX,WORD PTR[BP+4]"就可以把第一个参数值传给AX 寄存器.而需要留意的是C/C++传递参
数的顺序是和其它语言相反的.C语言是把参数的地址从右到左压入堆栈,所以越后面的参
数,在堆栈中的地址越靠前.

--

※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 219.217.235.95]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:6.340毫秒