Parameter passing
Parameters are transferred to procedures and functions via CPU registers or the stack, depending on the routine’s calling convention. For information about calling conventions, see
Calling conventions.
Variable (
var) parameters are always passed by reference, as 32-bit pointers that point to the actual storage location.
Value and constant (
const) parameters are passed by value or by reference, depending on the type and size of the parameter:
·
An ordinal parameter is passed as an 8-bit, 16-bit, 32-bit, or 64-bit value, using the same format as a variable of the corresponding type.
·
A real parameter is always passed on the stack. A
Single parameter occupies 4 bytes, and a
Double,
Comp, or
Currency parameter occupies 8 bytes. A
Real48 occupies 8 bytes, with the
Real48 value stored in the lower 6 bytes. An
Extended occupies 12 bytes, with the
Extended value stored in the lower 10 bytes.
·
A short-string parameter is passed as a 32-bit pointer to a short string.
·
A long-string or dynamic-array parameter is passed as a 32-bit pointer to the dynamic memory block allocated for the long string. The value
nil is passed for an empty long string.
·
A pointer, class, class-reference, or procedure-pointer parameter is passed as a 32-bit pointer.
·
A method pointer is passed on the stack as two 32-bit pointers. The instance pointer is pushed before the method pointer so that the method pointer occupies the lowest address.
·
Under the
register and
pascal conventions, a variant parameter is passed as a 32-bit pointer to a
Variant value.
·
Sets, records, and static arrays of 1, 2, or 4 bytes are passed as 8-bit, 16-bit, and 32-bit values. Larger sets, records, and static arrays are passed as 32-bit pointers to the value. An exception to this rule is that records are always passed directly on the stack under the
cdecl,
stdcall, and
safecall conventions; the size of a record passed this way is rounded upward to the nearest double-word boundary.
·
An open-array parameter is passed as two 32-bit values. The first value is a pointer to the array data, and the second value is one less than the number of elements in the array.
When two parameters are passed on the stack, each parameter occupies a multiple of 4 bytes (a whole number of double words). For an 8-bit or 16-bit parameter, even though the parameter occupies only a byte or a word, it is passed as a double word. The contents of the unused parts of the double word are undefined.
Under the
pascal,
cdecl,
stdcall and
safecall conventions, all parameters are passed on the stack. Under the
pascal convention, parameters are pushed in the order of their declaration (left-to-right), so that the first parameter ends up at the highest address and the last parameter ends up at the lowest address. Under the
cdecl,
stdcall, and
safecall conventions, parameters are pushed in reverse order of declaration (right-to-left), so that the first parameter ends up at the lowest address and the last parameter ends up at the highest address.
Under the
register convention, up to three parameters are passed in CPU registers, and the rest (if any) are passed on the stack. The parameters are passed in order of declaration (as with the
pascal convention), and the first three parameters that qualify are passed in the EAX, EDX, and ECX registers, in that order. Real, method-pointer, variant,
Int64, and structured types do not qualify as register parameters, but all other parameters do. If more than three parameters qualify as register parameters, the first three are passed in EAX, EDX, and ECX, and the remaining parameters are pushed onto the stack in order of declaration. For example, given the declaration
procedure Test(A: Integer;
var B: Char; C: Double;
const D:
string; E: Pointer);
a call to
Test passes A in EAX as a 32-bit integer, B in EDX as a pointer to a
Char, and D in ECX as a pointer to a long-string memory block; C and E are pushed onto the stack as two double-words and a 32-bit pointer, in that order.
Register saving conventions
Procedures and functions must preserve the EBX, ESI, EDI, and EBP registers, but can modify the EAX, EDX, and ECX registers. When implementing a constructor or destructor in assembler, be sure to preserve the DL register. Procedures and functions are invoked with the assumption that the CPU’s direction flag is cleared (corresponding to a CLD instruction) and must return with the direction flag cleared.
译文
参数传递
传递到过程和函数的参数,是经过CPU寄存器还是经过栈,取决于例程的调用约定。有关调用约定的信息见
调用约定。
变量参数(
var参数)总是通过一个引用被传递,该引用作为一个32位的指针,指向实际存储位置。
值参数和常量参数(
const参数)通过值或引用被传递,具体情况由参数的类型和尺寸决定:
·
序数型参数作为8位、16位、32位或64位值传递,其格式与相应类型的变量格式相同。
·
实数参数总是在栈中传递。
Single参数占用4个字节,
Double、
Comp或
Currency参数占用8个字节。
Real48占用8个字节,但它的值存储在低的6个字节中。
Extended占用12个字节,它的值存储在低的10个字节中。
·
短串参数作为32位的指针传递,该指针指向一个短串。
·
长串或动态数组参数作为32位指针传递,该指针指向分配给长串或动态数组的内存块。当长串或动态数组为空是,传递的值为
nil。
·
指针、类、类引用或过程指针参数坐位32位的指针传递。
·
方法指针作为32位的指针通过栈传递。实例指针在方法指针被推入栈之前被推入栈,因此方法指针占用最低的地址。
·
在
register和
pascal调用约定下,变体参数作为指向变体值的32位指针被传递。
·
1、2或4字节的集合、记录和静态数组作为8位、16位和32位的值被传递。更大的集合、记录和静态数组作为指向值的32位指针被传递。这一规则的例外之处是,在
cdecl、
stdcall以及
safecall调用约定下,记录总是直接在栈中传递,这时传递的记录的尺寸被向上舍入为双字边界。
·
开放数组参数作为两个32位的值被传递。第一个值是指向数组数据的指针,第二个值是一个比数组元素个数少1的值。
当两个参数通过栈被传递时,每个参数占用4字节的整倍数(整数或双字)。对于8位或16位参数,即使参数仅占用一个字节或一个字,但还是作为一个双字传递。双字中未使用的部分是未定义的。
在
pascal、
cdecl、
stdcall以及
safecall调用约定下,所有参数都在栈中传递。在
pascal约定下,参数按照其声明的顺序(自左到右)被推入栈,因此第一个参数最终在最高的地址而最后一个参数最终在最低的地址。在
cdecl、
stdcall和
safecall约定下,参数按照其声明的相反顺序(即自右到左)被推入栈,因此第一个参数最终在最低的地址而最后一个参数最终在最高的地址。
在
register约定下,一次最多可以在CPU寄存器中传递三个参数,剩下的(如果有)在栈中传递。参数传递按照其声明的顺序(与
pascal约定相同),并且前三个参数按照相同顺序传递到指定的EAX、EDX和ECX寄存器中。实数、方法指针、变体、
Int64以及结构类型不能限制为寄存器参数,除此之外的其他参数都可以。指定作为寄存器的参数如果多于三个,那么前三个被传递到EDA、EDX和ECX,剩余的参数被推到栈中,都是按照参数声明的顺序。例如,给出如下声明
procedure Test(A: Integer;
var B: Char; C: Double;
const D:
string; E: Pointer);
调用
Test时,参数A作为一个32位的整数被传递到EAX寄存器中,B作为一个
Char类型的字符被传递到EDX寄存器中,D作为指向长串内存块的指针被传递到ECX寄存器中,C和E依次作为两个双字和一个32位指针被推入栈中。
寄存器保存预定
过程和函数必须保持EBX、ESI、EDI和EBP寄存器,但可以修改EAX、EDX和ECX寄存器。当在内嵌汇编程序中实现构造器或析构器时,应确认保持DL寄存器。假设过程和函数被调用时,CPU的方向标记是清除的(相应的一个CLD指令),那么必须返回清除的方向标记。