发件人:scs@adam.mit.edu (Steve Summit)
主题:回复:动态函数调用
日期:1993 年 3 月 7 日星期日 17:58:52 -0500
Message-Id: <9303072258.AA22973@adam.MIT.EDU>
你写道
> 我很好奇您对以下问题的看法
> 出现在 C 语言 FAQ 中
>
> 7.5:如何在运行时构建参数列表来调用函数?
> A:没有保证或可移植的方法可以做到这一点。如果您对此感到好奇,请询问此列表的编辑,他有一些您可能想尝试的“古怪想法”(另请参阅问题 16.10)。
>
> A: 没有保证或可移植的方法可以做到这一点。如果您对此感到好奇,请询问此列表的编辑,他有一些您可能想尝试的“古怪想法”(另请参阅问题 16.10)。
> A: 没有保证或可移植的方法可以做到这一点。如果您对此感到好奇,请询问此列表的编辑,他有一些您可能想尝试的“古怪想法”(另请参阅问题 16.10)。
> A: 没有保证或可移植的方法可以做到这一点。如果您对此感到好奇,请询问此列表的编辑,他有一些您可能想尝试的“古怪想法”(另请参阅问题 16.10)。
[此问题的当前版本,现编号为 15.13]
信不信由你,你是第一个问这个问题的人,所以你将是第一个听到我为此道歉的人,因为我过去写的关于那些“古怪想法”的几个详尽的讨论实际上都卡在我磁带档案的某个地方,不容易获取。
[我可能想到的两个主要讨论是 这个 和 这个。]
这是一个大纲;我稍后会尝试找到我写的一个较长的旧描述并发送它。
基本思想是假设存在以下例程
#include <stdarg.h> callg(funcp, argp) int (*funcp)(); magic_arglist_pointer argp;
该例程调用由以下指向的函数funcp,将参数列表指向的参数传递给它argp。它返回所指向的函数返回的任何内容。
第二个问题当然是如何构建指向的参数列表argp。多年来我有很多关于如何做到这一点的想法;也许最好的(或者至少是我目前最满意的)是这样做
extern func(); va_alloc(arglist, 10); va_list argp; va_start2(arglist, argp); va_arg(argp, int) = 1; va_arg(argp, double) = 2.3; va_arg(argp, char *) = "four"; va_finish2(arglist, argp); callg(func, arglist);
上述相当于简单的调用
func(1, 2.3, "four");
现在,有趣的是,通常(甚至可能是“通常”)可以构造va_alloc, va_start2,而va_finish2宏,正如我上面所示,标准va_arg宏来自<stdarg.h>或<varargs.h>可以完成实际工作。(换句话说,传统的实现va_arg实际上可以在左值上下文中工作,即在赋值的左侧。)
我非常确定我拥有类似于va_alloc, va_start2,而va_finish2宏的宏的有效版本,但我现在也找不到它们。在这条消息的末尾,我将附加一套不同的宏,它们不基于能够重新使用现有的va_arg宏在左侧,这应该作为基本实现思想的示例。
第三个问题是,如果被调用函数的返回类型(不仅仅是参数列表)在编译时未知,该怎么办?(如果您还在听,我们正朝着在运行时做大量事情的方向发展,我们几乎停止了编译,开始了解释,事实上,我在这篇笔记中讨论的许多想法都来自我编写一个完整的 C 解释器的尝试。)我们可以通过向我们假设的callg()例程添加第三个参数来回答第三个问题,其中包含一个描述我们期望的结果类型的标签和一个可以存储任何返回值并集。
第四个问题是如何编写这个假设的callg()函数。它是我最喜欢的函数示例之一,它基本上必须用汇编语言编写,不是为了效率,而是因为它根本无法用 C 来完成。实际上它并不难写;我为我使用的大多数机器都有它的实现。我通过编译一个简单的程序片段来为新环境编写它,例如
int nargs; int args[10]; int (*funcp)(); int i; switch(nargs) { case 0: (*funcp)(); break; case 1: (*funcp)(args[0]); break; case 2: (*funcp)(args[0], args[1]); break; ... for(i = 0; i < nargs; i++) (*funcp)(args[i]);
,并整理汇编语言输出,以便在执行(单个)调用之前推送可变数量的参数。在不了解所使用的汇编/机器语言的太多信息的情况下,可以做到这一点。(能够查看其生成的汇编语言的编译器输出或列表选项非常有帮助。)
当我说通常很容易写callg时,我想到的是传统的基于堆栈的机器。一些现代机器在寄存器中传递部分或全部参数,并且使用的寄存器可能取决于参数的类型,这使得这类事情更加困难。
第五个问题是我的名字“callg”来自哪里。VAX 有一个单一的指令 CALLG,它正是您想要的。(换句话说,这个callg()例程的汇编语言实现是 VAX 上的一行汇编。)我也用过这个名字va_call来解决这个问题,而不是callg.
如果您对此有任何疑问或评论,或者您想看更多代码片段,请随时提出。
史蒂夫·萨米特