如何在 C 中实现 stdarg

How to implement stdarg in C

出于好奇,我希望为标准 C 库中的某些函数编写最少的替代品。到目前为止,我已经完成了 printf()strlen()strcpy()memcpy()memset() 等...但是当我尝试使用 printf 函数时,我不知道如何实施stdarg.h!有什么方法可以做到这一点?

它使用宏还是实际函数?

我正在 32 位 x86 上使用 gccclang,如果它有助于使这个问题更容易回答的话。

在具有 cdecl 调用约定的 32 位 x86 上,参数在堆栈上传递:

^ higher addresses (lower on the stack)
|
| caller local variables
| ...
| argument 3
| argument 2
| argument 1
| return address
| saved EBP (usually)
| callee local variables
|
v lower addresses (higher on the stack)

您可以将 va_list 实现为指针。 va_start 可以获取传递给它的参数的地址并添加该参数的大小以移动到下一个参数。 va_arg 可以访问指针并将其撞到下一个参数。 va_copy 可以只复制指针值。 va_end不需要做任何事情。

另一方面,如果你没有使用 cdecl(也许你正在使用 fastcall),你不是 32 位的,你不是 x86,这将不起作用;您可能需要处理寄存器而不仅仅是指针值。甚至它仍然不能保证工作,因为你依赖于未定义的行为;作为仅一个潜在问题的示例,内联可能会毁掉一切。这就是为什么头文件只是 typedef 将其发送到内置编译器的原因——在 C 中实现它是没有希望的,你需要编译器支持。甚至不要让我开始实施 setjmplongjmp

您可以查看如何实现 va 宏的示例 here。 header 在 VC++ 中使用,每个处理器架构都有不同的实现。这些宏似乎并不特定于 Microsoft 编译器。在 GCC 和 Clang 中,va 宏都引用编译器 built-in 函数。

无法在 C 中实现 stdarg.h 宏;您需要 GCC 和兼容编译器提供的编译器内置函数,例如 __builtin_va_arg 等,或者您的编译器的等效项。

即使您知道正在使用的特定目标的参数传递约定(如 icktoofay 的回答中的 i386),在 C 中也无法访问此内存。简单地对传递给 va_start 的地址执行指针运算是无效的;它会导致未定义的行为。但是,即使 C 确实允许该算法,也不能保证最后一个命名参数的地址实际上对应于作为调用约定的一部分在堆栈上传递的位置;编译器可以选择将其移动到堆栈帧中的不同位置(可能是为了获得额外的对齐或数据局部性)。

1992 年发布到 comp.sources.unix 的 CALC 源代码中有一个实现。

这是来自 shar 存档,因此请忽略 Xs。

X * Copyright (c) 1992 David I. Bell
X * Permission is granted to use, distribute, or modify this source,
X * provided that this copyright notice remains intact.

X/*
X * SIMULATE_STDARG
X *
X * WARNING: This type of stdarg makes assumptions about the stack
X *             that may not be true on your system.  You may want to
X *            define STDARG (if using ANSI C) or VARARGS.
X */
X
Xtypedef char *va_list;
X#define va_start(ap,parmn) (void)((ap) = (char*)(&(parmn) + 1))
X#define va_end(ap) (void)((ap) = 0)
X#define va_arg(ap, type) \
X    (((type*)((ap) = ((ap) + sizeof(type))))[-1])