如何在C中实现stdarg
为了好奇,我想为标准C库中的一些函数编写最小的替换。 到目前为止,我已经完成了printf()
, strlen()
, strcpy()
, memcpy()
, memset()
等…但是当我尝试使用printf函数时,我不知道如何实现stdarg.h
! 我能做到这一点的方式是什么?
它是使用宏还是实际function?
我在32位x86上使用gcc
OR clang
,如果它有助于使这更容易回答。
在具有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,这是行不通的; 您可能需要使用寄存器而不仅仅是指针值。 即使它仍然不能保证工作,因为你依赖于未定义的行为; 作为一个潜在问题的例子,内联可能会破坏一切。 这就是为什么头文件只是对内置的编译器进行类型定义的原因 – 在C中实现它是没有希望的,你需要编译器支持。 甚至不让我开始实现setjmp
和longjmp
……
无法在C中实现stdarg.h
宏; 你需要像__builtin_va_arg
这样的编译器内置__builtin_va_arg
,GCC和兼容的编译器提供的,或者你的编译器的等价物。
即使你知道你正在使用的特定目标传递约定的参数(比如icktoofay的答案中的i386),C中也无法访问这个内存。 简单地对传递给va_start
的地址执行指针算法是无效的; 它会导致未定义的行为。 但即使C确实允许该算术,也不能保证最后一个命名参数的地址实际上对应于它作为调用约定的一部分在栈上传递的位置; 编译器可以选择将其移动到其堆栈帧中的不同位置(可能是为了获得额外的对齐或数据位置)。
在1992年发布到comp.sources.unix的CALC源代码中有一个实现。
这是来自shar存档,所以忽略X
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])
您可以在此处查看如何实现va宏的示例。 此标头用于VC ++,每个处理器体系结构都有不同的实现。 这些宏似乎并不特定于Microsoft编译器。 在GCC和Clang中,va宏指的是编译器内置函数。