printf - 从 C 中的可变参数函数的参数确定类型

标签 printf variadic-functions

我想要逐步解释如何解析可变参数函数的参数 这样,当调用 va_arg(ap, TYPE); 时,我会传递所传递参数的正确数据类型。

目前我正在尝试编写 printf 代码。 我只是寻找解释,最好带有简单的示例,但不是 printf 的解决方案,因为我想解决我自己。

以下是三个看起来像我正在寻找的示例:

  1. https://stackoverflow.com/a/1689228/3206885
  2. https://stackoverflow.com/a/5551632/3206885
  3. https://stackoverflow.com/a/1722238/3206885

我了解 typedefstructenumunion 的基础知识,但无法理解出一些实际的应用案例,例如链接中的示例。

它们的真正含义是什么?我无法理解它们是如何工作的。 如何将数据类型从联合传递到 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);
}

最佳答案

您无法从 va_list 获取实际类型信息。您可以从 format 中获取所需内容。您似乎没有想到的是:没有一个参数知道实际类型是什么,但是format代表了调用者对类型应该<的想法/em> 是。 (也许进一步提示:如果调用者提供的格式说明符与传入的可变参数不匹配,实际 printf 会做什么?它会注意到吗?)

您的代码必须解析“%”格式说明符的格式字符串,并使用这些说明符分支到读取具有特定硬编码类型的 va_list。例如, (伪代码) if (fspec was "%s") { char* str = va_arg(ap, char*);打印出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
}

通过采用显式的 intchar* 参数,这两个专用函数可以单独正常工作;我们进行联合的原因是为了使函数能够正式采用相同类型的参数,以便它们具有相同的签名,以便我们可以定义一个单一类型,这意味着指向该种函数的指针:

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);

(是的,这些名称都是故意 super 冗长的。您可能希望在实际程序中使用较短的名称,甚至在适当的情况下使用匿名类型。)

现在您可以使用该数组在运行时选择正确的格式化程序:

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, ...) 。编写完成后,您可能会发现自己实际上并不需要联合或结构数组。

关于printf - 从 C 中的可变参数函数的参数确定类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27475510/

相关文章:

bash - 如何将 printf 语句的结果分配给变量(前导零)?

c - 格式化打印显示意外结果

c - 如何在C中垂直打印?

c - 使用 vfprintf 而不使用包装函数?

java - 什么时候在 Java 中使用可变参数?

c++ - 在参数 C++ 中传递多个参数而不使用 va_list

c - 可变参数必须是可变参数函数中的第二个参数吗?

c - 正在使用未初始化变量 UB 的地址吗?

c++ - variadic template模板的完美转发

iphone - 如何 "pass on"可变数量的参数到 NSString 的 +stringWithFormat :