Next: , Previous: Stack and Calling, Up: Target Macros


17.11 实现Varargs宏

GCC自带了<varargs.h><stdarg.h>的实现, 可直接用于在栈上传递参数的机器上。其它机器需要它们自己的varargs实现, 并且两个机器独立的头文件必须条件包含它。

ISO <stdarg.h>与传统<varargs.h>的区别主要在va_start的调用 约定上。传统的实现只接受一个参数,其为存储参数指针的变量。 ISO的实现接受额外的第二个参数。用户应该将函数的最后一个命名参数写在这里。

然而,va_start不应该使用这个参数。 发现命名参数结尾的方法为使用下面描述的内建函数。

— Macro: __builtin_saveregs ()

使用该内建函数来将参数寄存器保存在内存中,使得varargs机制可以访问它们。 va_start的ISO版本和传统版本都必须使用__builtin_saveregs, 除非你使用TARGET_SETUP_INCOMING_VARARGS来替代(参见下面)。

在一些机器上,__builtin_saveregs为开放编码的, 在target钩子TARGET_EXPAND_BUILTIN_SAVEREGS的控制下。 在其它机器上,其调用了汇编语言写的例程,可以在libgcc2.c中找到。

不管怎样,调用__builtin_saveregs的生成代码都出现在函数的起始处。 这是因为寄存器必须在函数开始使用它们前被保存。

— Macro: __builtin_next_arg (lastarg)

这与__builtin_args_info等价,用于栈参数。其返回第一个匿名栈参数的地址, 类型为void *。如果ARGS_GROW_DOWNWARD, 其返回第一个匿名栈参数的上面的位置地址。在va_start中使用它来初始化指针, 来从栈中获得参数。同样,在va_start中使用它来验证第二个参数lastarg 为当前函数的最后一个命名参数。

— Macro: __builtin_classify_type (object)

由于每个机器具有它自己的约定,对于哪些数据类型在哪种寄存器中传递, 因此你的va_arg实现必须包含这些约定。 将指定数据类型归类的最简单方法是使用__builtin_classify_type, 加上sizeof__alignof__

__builtin_classify_type忽略object的值,只考虑它的数据类型。 其返回一个整数来描述什么类型为,整型,浮点,指针,结构体等。

文件typeclass.h定义了一个枚举, 你可以用来解析__builtin_classify_type的值。

这些机器描述宏用来帮助实现varargs:

— Target Hook: rtx TARGET_EXPAND_BUILTIN_SAVEREGS (void)

如果定义,该钩子产生机器特定代码,用于调用__builtin_saveregs。 该代码将被移动到函数的最开始处,在访问任何参数之前。 该函数的返回值应该为一个RTX,其包含了__builtin_saveregs的返回值。

— Target Hook: void TARGET_SETUP_INCOMING_VARARGS (CUMULATIVE_ARGS *args_so_far, enum machine_mode mode, tree type, int *pretend_args_size, int second_time)

该target钩子提供了使用__builtin_saveregs和定义 TARGET_EXPAND_BUILTIN_SAVEREGS钩子的替代。 使用它来将匿名寄存器参数存储到栈中,使得所有参数都像是通过栈连续的传递。 当这样做时,你可以使用varargs的标准实现,其用于将所有参数在栈上传递的机器上。

参数args_so_far指向CUMULATIVE_ARGS数据结构体, 包含了处理完命名参数之后所获得的值。 参数modetype描述了最后一个命名参数的机器模式和作为树结点的数据类型。

该target钩子应该做两件事:第一,将所有不用于命名参数的参数寄存器压入栈中, 第二,存储数据的大小,把pretend_args_size指向的int值得变量压入。 这里你存储的值将作为额外的偏移量,用来建立栈帧。

因为你必须生成代码来将匿名参数在编译时压入,而不需要知道它们的数据类型, 所以TARGET_SETUP_INCOMING_VARARGS只在只有一种参数寄存器类别并用于所有 数据类型的机器上有用。

如果参数second_time非0,其以为着函数的参数被第二次分析。 这发生于内联函数,其直到源文件结尾才被实际编译。对于这种情况, 钩子TARGET_SETUP_INCOMING_VARARGS不应该产生任何指令。

— Target Hook: bool TARGET_STRICT_ARGUMENT_NAMING (CUMULATIVE_ARGS *ca)

定义该钩子来返回true,如果函数参数传递的位置依赖于其是否为一个命名参数。

该钩子控制对于varargs和stdarg函数,如何设置FUNCTION_ARG的named参数。 如果该钩子返回true,则named参数总是为命名参数,未命名参数总是未假。 如果返回false,但是TARGET_PRETEND_OUTGOING_VARARGS_NAMED返回 true,则所有参数都被作为命名的对待。否则所有命名参数,除了最后一个, 被作为命名的对待。

如果其总是返回0,则不需要定义该钩子。

— Target Hook: bool TARGET_PRETEND_OUTGOING_VARARGS_NAMED

如果你需要条件的改变ABI,使得一种工作于TARGET_SETUP_INCOMING_VARARGS, 另一种工作于TARGET_SETUP_INCOMING_VARARGSTARGET_STRICT_ARGUMENT_NAMING都没有被定义,则定义该钩子返回true, 如果使用TARGET_SETUP_INCOMING_VARARGS,否则返回false。 否则,你不需要定义该钩子。