1、汇编语言的过程调用与 c 语言的函数调用姓名:孙贵森学号:201212301118汇编语言的过程调用,如果需要传递参数,一般有 2 种方法,通过寄存器来“传递” ,或是通过参数来传递。 (还有将所有参数制成参数列表并压栈的传递方法,但较少用。)通过寄存器来“传递” ,不是真正意义上的传递,其只不过是事先在几个有限的 CPU 寄存器中设置相应的值后,再调用过程,过程再直接读取这些寄存器的内容。可想而知,此法犹如 C 语言中的全局变量,极易感染。而如果通过参数来传递,又不得不面临手工维护堆栈框架(stack frame)的重担。堆栈框架动态地存放着参数、调用过程的返回地址、过程局部变量、过程内的压
2、栈等内容,也是不好对付的。一般情况下,一个普通的过程可能如下编写:Sum PROCpush ebpmov ebp, esp .pop ebpretSum ENDP作为遵从 C 调用约定(Calling Convention)调用者,则需这样调用上述过程:push 5 ;push 8;call Sum ; add esp, 4 * 2; 而如果遵从 STDCALL 调用约定,则:Sum PROCpush ebpmov ebp, espmov eax, ebp + 12 ;add eax, ebp + 8;pop ebpret 4 * 2 ;Sum ENDPSum PROCpush ebpmov
3、ebp, essub esp, 8 ;mov eax, ebp + 12;add eax, ebp + 8;add eax, ebp - 4;add eax, ebp - 8; mov esp, ebp;pop eret 4 * 2; Sum ENDP在被调用的过程内,分为 3 种情况:1. 无参数,也无局部变量2. 有参数3. 有局部变量当无参数且无局部变量时,堆栈中只是保存 call 语句的下一条语句的地址,可以很安全地返回。而当有参数,使用 PROC 伪指令的接收参数的形式,MASM 则会自动生成正确的返回代码.而当有局部变量,使用LOCAL 伪指令来定义局部变量,MASM 也会自动地生
4、成正确的返回代码。在将参数压栈时,仍需将其打包为 32 位的,dataval1 WORD 19 ;.codemovzx eax, val1 ;push eax ; 另一选择是,将用作 argument 的变量声明为 DWORD.dataval1 DWORD 19;.codepush val1 ;还有另一种方法,即,总是传递指针。.dateval1 WORD 5val2 WORD 10val3 WORDemain PROE push OFFSET val2push OFFSET val1call Sum ; sum(5, 10)mov val3, ax ; receive the return
5、value of Sumexitmain ENDPSum PROC,pV1:PTR WORD,pV2:PTR WORD,mov esi, pV1mov ax, word ptr esimov edi, pV2add ax, word ptr ediretSum ENDP这种方法在保留了我们可以声明仅需的变量类型的同时,也确保argument32 位的方法正确压栈。C 语言中的每一个函数都是一个独立的代码块。一个函数的代码块是隐藏于函数内部的,不能被任何其它函数中的任何语句(除调用它的语句之外)所访问(例如,用 g o t o 语句跳转到另一个函数内部是不可能的) 。构成一个函数体的代码对程序的
6、其它部分来说是隐蔽的,它既不能影响程序其它部分,也不受其它部分的影响。换言之,由于两个函数有不同的作用域,定义在一个函数内部的代码数据无法与定义在另一个函数内部的代码和数据相互作用。C 语言中所有的函数都处于同一作用域级别上。这就是说,把一个函数定义于另一个函数内部是不可能的.量在函数内部定义的变量成为局部变量。在某些 C 语言教材中,局部变量称为自动变量,这就与使用可选关键字 a u t o 定义局部变量这一作法保持一致。局部变量仅由其被定义的模块内部的语句所访问。换言之,局部变量在自己的代码模块之外是不可知的。括号开始,以右花括号结束.对于局部变量,要了解的最重要的东西是:它们仅存在于被定
7、义的当前执行代码块中,即局部变量在进入模块时生成,在退出模块时消亡。定义局部变量的最常见的代码块是函数。例如,考虑下面两个函数.整数变量 x 被定义了两次,一次在 func1()中,一次在 func2()中。func1()和 func2()中的 x 互不相关。其原因是每个 x 作为局部变量仅在被定义的块内可知。语言中包括了关键字 auto,它可用于定义局部变量。但自从所有的非全局变量的缺省值假定为 auto 以来,auto 就几乎很少使用了,因此在本书所有的例子中,均见不到这一关键字。在每一函数模块内的开始处定义所有需要的变量,是最常见的作法。这样做使得任何人读此函数时都很容易,了解用到的变量
8、。但并非必须这样做不可,因为局部变量可以在任何模块中定义。这里的局部变量 s 就是在 if 块入口处建立,并在其出口处消亡的。因此 s 仅在if 块中可知,而在其它地方均不可访问,甚至在包含它的函数内部的其它部分也不行。在一个条件块内定义局部变量的主要优点是仅在需要时才为之分配内存。这是因为局部变量仅在控制转到它们被定义的块内时才进入生存期。虽然大多数情况下这并不十分重要,但当代码用于专用控制器(如识别数字安全码的车库门控制器)时,这就变得十分重要了,因为这时随机存储器(RAM)极其短缺。由于局部变量随着它们被定义的模块的进出口而建立或释放,它们存储的信息在块工作结束后也就丢失了。切记,这点对
9、有关函数的访问特别重要。当访问一函数时,它的局部变量被建立,当函数返回时,局部变量被销毁。这就是说,局部变量的值不能在两次调用之间保持。与局部变量不同,全局变量贯穿整个程序,并且可被任何一个模块使用。它们在整个程序执行期间保持有效。全局变量定义在所有函数之外,可由函数内的任何表达式访问。在下面的程序中可以看到,变量 count 定义在所有函数之外,函数 main()之前。但其实它可以放置在任何第一次被使用之前的地方,只要不在函数内就可以。实践表明,定义全局变量的最佳位置是在程序的顶部。仔细研究此程序后,可见变量 count 既不是 main()也不是 func1()定义的,但两者都可以使用它。
10、函数 func2()也定义了一个局部变量 count。当 func2 访问count 时,它仅访问自己定义的局部变量 count,而不是那个全局变量 count。切记,全局变量和某一函数的局部变量同名时,该函数对该名的所有访问仅针对局部变量,对全局变量无影响,这是很方便的。然而,如果忘记了这点,即使程序看起来是正确的,也可能导致运行时的奇异行为。全局变量由 C 编译程序在动态区之外的固定存储区域中存储。当程序中多个函数都使用同一数据时,全局变量将是很有效的。然而,由于三种原因,应避免使用不必要的全局变量:不论是否需要,它们在整个程序执行期间均占有存储空间。由于全局变量必须依靠外部定义,所以在使
11、用局部变量就可以达到其功能时使用了全局变量,将降低函数的通用性,这是因为它要依赖其本身之外的东西。大量使用全局变量时,不可知的和不需要的副作用将可能导致程序错误。如在编制大型程序时有一个重要的问题:变量值都有可能在程序其它地点偶然改变。结构化语言的原则之一是代码和数据的分离。C 语言是通过局部变量和函数的使用来实现这一分离的。下面用两种方法编制计算两个整数乘积的简单函数 mul()通用的专用的 mul(x,y) intx,y;intx,y; mul()return(x*y);return(x*y);两个函数都是返回变量 x 和 y 的积,可通用的或称为参数化版本可用于任意两整数之积,而专用的版
12、本仅能计算全局变量 x 和 y 的乘积。从变量的作用域原则出发,我们可以将变量分为全局变量和局部变量;换一个方式,从变量的生存期来分,可将变量分为动态存储变量及静态存储变量。动态存储变量可以是函数的形式参数、局部变量、函数调用时的现场保护和返回地址。这些动态存储变量在函数调用时分配存储空间,函数结束时释放存储空间。动态存储变量的定义形式为在变量定义的前面加上关键字“auto” ,例如:auto int a,b,c“auto”也可以省略不写。事实上,我们已经使用的变量均为省略了关键字“auto”的动态存储变量。有时我们甚至为了提高速度,将局部的动态存储变量定义为寄存器型的变量,定义的形式为在变量
13、的前面加关键字“register” ,例如:register int x,y,z;这样一来的好处是:将变量的值无需存入内存,而只需保存在 CPU 内的寄存器中,以使速度大大提高。由于 CPU 内的寄存器数量是有限的,不可能为某个变量长期占用。因此,一些操作系统对寄存器的使用做了数量的限制。或多或少,或根本不提供,用自动变量来替代在编译时分配存储空间的变量称为静态存储变量,其定义形式为在变量定义的前面加上关键字“static” ,例如:static int a=8;定义的静态存储变量无论是做全程量或是局部变量,其定义和初始化在程序编译时进行。作为局部变量,调用函数结束时,静态存储变量不消失并且保留原值。从上述程序看,函数 f()被三次调用,由于局部变量 x 是静态存储变量,它是在编译时分配存储空间,故每次调用函数 f()时,变量 x 不再重新初始化,保留加 1 后的值,得到上面的输出。