我想逐步解释如何解析可变参数函数的参数 这样当调用va_arg(ap,TYPE)时;我传递了传递的参数的正确数据TYPE。
目前我正在尝试编写printf代码。 我只是寻找解释最好用用简单的例子但不是解决方案到printf,因为我想自己解决这个个人挑战。
Link1,link2和link3是我认为属于我所看到的一部分的示例。 我是typedef,struct,enum和union所做的基础,但是可以找出一些实际的应用案例,比如链接中的例子。
他们真正的意思是什么?我不能围绕它们的工作方式包裹我的大脑。 如何将联合中的数据类型传递给va_arg,如链接示例中所示?它是如何匹配的? 使用修饰符如%d,%i ...或参数的数据类型?
这是我到目前为止所得到的:
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include "my.h"
typedef struct s_flist
{
char c;
(*f)();
} t_flist;
int my_printf(char *format, ...)
{
va_list ap;
int i;
int j;
int result;
int arg_count;
char *cur_arg = format;
char *types;
t_flist flist[] =
{
{ 's', &my_putstr },
{ 'i', &my_put_nbr },
{ 'd', &my_put_nbr }
};
i = 0;
result = 0;
types = (char*)malloc( sizeof(*format) * (my_strlen(format) / 2 + 1) );
fparser(types, format);
arg_count = my_strlen(types);
while (format[i])
{
if (format[i] == '%' && format[i + 1])
{
i++;
if (format[i] == '%')
result += my_putchar(format[i]);
else
{
j = 0;
va_start(ap, format);
while (flist[j].c)
{
if (format[i] == flist[j].c)
result += flist[i].f(va_arg(ap, flist[i].DATA_TYPE??));
j++;
}
}
}
result += my_putchar(format[i]);
i++;
}
va_end(ap);
return (result);
}
char *fparser(char *types, char *str)
{
int i;
int j;
i = 0;
j = 0;
while (str[i])
{
if (str[i] == '%' && str[i + 1] &&
str[i + 1] != '%' && str[i + 1] != ' ')
{
i++;
types[j] = str[i];
j++;
}
i++;
}
types[j] = '\0';
return (types);
}
答案 0 :(得分:2)
您无法从va_list
获取实际的类型信息。您可以从format
获取所需内容。您似乎没有想到的是:知道实际类型的参数都没有,但是format
表示调用者对应该的类型的看法是。 (也许还有一个提示:如果调用者给它的格式说明符与传入的varargs不匹配,实际 printf
会怎么做?会注意到吗?)
您的代码必须解析“%”格式说明符的格式字符串,并使用这些说明符分支以读取具有特定硬编码类型的va_list
。例如,(伪代码)if (fspec was "%s") { char* str = va_arg(ap, char*); print out str; }
。没有给出更多细节,因为你明确表示你不想要一个完整的解决方案。
编辑添加:
您永远不会将类型作为一段运行时数据,您可以将其作为值传递给va_arg
。 va_arg
的第二个参数必须是在编译时引用已知类型的文字硬编码规范。 (注意va_arg
是一个在编译时被扩展的宏,而不是在运行时被执行的函数 - 你没有一个函数将类型作为参数。)
您的一些链接建议通过枚举跟踪类型,但这只是为了您自己的代码能够根据该信息进行分支;它仍然不能传递给va_arg
。您必须使用单独的代码片段va_arg(ap, int)
和va_arg(ap, char*)
,因此无法避免switch
或if
链。
使用联合和结构,您想要制作的解决方案将从以下内容开始:
typedef union {
int i;
char *s;
} PRINTABLE_THING;
int print_integer(PRINTABLE_THING pt) {
// format and print pt.i
}
int print_string(PRINTABLE_THING pt) {
// format and print pt.s
}
这两个专门的函数可以通过明确的int
或char*
参数自行运行。我们建立联合的原因是使函数能够正式采用相同类型的参数,以便它们具有相同的签名,这样我们就可以定义一个单独的类型,这意味着指向这种函数的指针:
typedef int (*print_printable_thing)(PRINTABLE_THING);
现在你的代码可以有一个类型为print_printable_thing
的函数指针数组,或者一个结构数组,其中print_printable_thing
作为结构域之一:
typedef struct {
char format_char;
print_printable_thing printing_function;
} FORMAT_CHAR_AND_PRINTING_FUNCTION_PAIRING;
FORMAT_CHAR_AND_PRINTING_FUNCTION_PAIRING formatters[] = {
{ 'd', print_integer },
{ 's', print_string }
};
int formatter_count = sizeof(formatters) / sizeof(FORMAT_CHAR_AND_PRINTING_FUNCTION_PAIRING);
(是的,这些名字都是故意超级详细的。你可能想要真正的程序中更短的名字,甚至是适当的匿名类型。)
现在您可以使用该数组在运行时选择正确的格式化程序:
for (int i = 0; i < formatter_count; i++)
if (current_format_char == formatters[i].format_char)
result += formatters[i].printing_function(current_printable_thing);
但是将正确的内容放入current_printable_thing
的过程仍然需要分支才能使用正确的硬编码类型进入va_arg(ap, ...)
。一旦你编写它,你可能会发现自己决定实际上不需要union和结构数组。