阅读(3316) (0)

Assembly 可重入和递归子程序

2016-10-29 11:18:58 更新

一个可重入子程序必须满足下面几个性质:


1、它不能修改代码指令。在高级语言中,修改代码指令是非常难的;但是在汇编语言中,一个程序要修改自己的代码并不是一件很难的事。


例如:


mov        word [cs:$+7], 5           ; 将5复制到前面七个字节的字中
add         ax, 2                           ; 前面的语句将2改成了5!

这些代码在实模式下可以运行,但是在保护模式下的操作系统上不行,因为代码段被标识为只读。在这些操作系统上,当执行了上面的第一行代码,程序将被终止。这种类型的程序从各个方面来看都非常差。它很混乱,很难维护而且不允许代码共享(看下面)。


2、它不能修改全局变量(比如在data和bss段里的数据)。所有的变量应储存在堆栈里。


书写可重入性代码有几个好处。


1、一个可重入子程序可以递归调用。


2、 一个可重入程序可以被多个进程共享。在许多多任务操作系统上,如果一个程序有许多实例正在运行,那么只有一份代码的拷贝在内存中。共享库和DLL(Dynamic Link Libraries,动态链接库)同样使用了
这种技术。


3、可重入子程序可以运行在多线程5 程序中。Windows 9x/NT和大多数类UNIX操作系统(Solaris,Linux,等)都支持多线程程序。


递归子程序

这种类型的子程序调用它们自己。递归可以是直接的或是间接的。当一个名为foo的子程序在foo内部调用自己就产生直接递归。当一个子程序虽然自己没有直接调用自己,但是其它子程序调用了它,就产生间接递归。

例如:子程序foo可以调用bar且bar也可以调用foo。

递归子程序必须有一个终止条件。当这个条件为真时,就不再进行递归调用了。如果一个子程序没有终止条件或条件永不为真,那么递归将不会结束(非常像一个无穷循环)。



图4.15展示了一个递归求n!的函数。在C中它可以这样被调用:

x = fact (3);           /* find 3! */

图4.16展示了上面的函数调用的最深点的堆栈状态。


图4.17展示了另一个更复杂的递归样例的C语言版而4.18展示了它的汇编语言版。对于f(3),输出是什么?注意:每一次递归调用,ENTER指令都会在堆栈上给新的i值分配空间。因此,f的每一次递归调用都有它自己独立的变量i。若是在data段定义i为一双字,结果就不一样了。





回顾一下C变量的储存类型

C提供了几种变量储存类型。

global,全局 这些变量定义在任何函数的外面,且储存在固定的内存空间中(在data或bss段),而且从程序的开始一直到程序的结束都存在。缺省情况下,它们能被程序中的任何一个函数访问;但是,如果它们被声明为static,那么只有在同一模块中的函数才能访问它们(也就是说, 依照汇编的术语,这个变量是内部的,不是外部的)。

static,静态 在一个函数中,它们是被声明为静态的局部变量。(不幸的是,C使用关键字static有两种目的!)这些变量同样储存在固定的内存空间中(在data或bss段),但是只能被定义它的函数直接访问。

automatic,自动 它是定义在一个函数内的C变量的缺省类型。当定义它们的函数被调用了,这些变量就被分配在堆栈上,而当函数返回了又从堆栈中移除。因此,它们没有固定的内存空间。

register,寄存器 这个关键字要求编译器使用寄存器来储存这个变量的数据。这仅仅是一个要求。编译器并不一定要遵循。如果变量的地址使用在程序的任意的地方,那么就不会遵循(因为寄存器没有地址)。同样,只有简单的整形数据可以是寄存器变量。结构类型不可以;因为它们的大小不匹配寄存器!C编译器通常会自动将普通的自动变量转换成寄存器变量,而不需要程序员给予暗示。

volatile,不稳定 这个关键字告诉编译器这个变量值随时都会改变。这就意味着当变量被更改了,编译器不能做出任何推断。通常编译器会将一个变量的值暂时存在寄存器中,而且在出现这个变量的代码部分使
用这个寄存器。但是,编译器不能对不稳定类型的变量做这种类型的优化。一个不稳定变量的最普遍的例子就是:它可以被多线程程序的两个线程修改。考虑下面的代码:

1  x = 10;
2  y = 20;
3  z = x;

如果x可以被另一个线程修改。那么其它线程可以会在第1行和第3行之间修改x的值,以致于z将不会等于10.但是,如果x没有被声明为不稳定类型,编译器就会推断x没有改变,然后再将z置为10。

不稳定类型的另一个使用就是避免编译器为一个变量使用一个寄存器。