【问题标题】:How to create user defined format specifiers in C如何在 C 中创建用户定义的格式说明符
【发布时间】:2022-01-04 15:15:02
【问题描述】:

我是 C 编程新手。我很好奇是否可以制作自己的用户定义格式说明符。但是,不幸的是我不知道从哪里开始。有人可以指导我。 我从某处读到 C 的头文件也是用 C 编写的。那么有没有可能像从那里复制代码并修改它来完成工作? 我的意思是说我已经定义了一个结构:

struct data
{
    int d;
    char str;
};

现在我们已经对它进行了 typedef 来表示数据:

typedef struct data data;

我想要的是什么时候我将编写打印语句,如果我已经将数据的格式说明符设置为 %q 那么:

printf("%q",x);   // Where x is a data type variable.

它会自己打印 int 部分,然后是 String 部分。

【问题讨论】:

  • 你不能,除非你对 printf 做了一个包装。在这种情况下,提出您的自定义打印功能可能是一个更好的主意。
  • char *fmt = build_format_string(); printf(fmt, x);请注意将类型与格式字符串匹配。 (printf 可能会在不同类型的 switch 中。)
  • 头文件不包含实现,只包含函数签名(printf 的第一个参数是指向 const char 的简单指针,不知道转换说明符,其他参数是未知)。 实现 在 C 标准库中。理论上,您可以提取 printf 版本的实现(例如,来自 gnu 标准 C 库,github.com/lattera/glibc/tree/master/stdio-common),对其进行修改并使其成为您项目的一部分。但我会考虑让它正确编译和链接一个专家任务和矫枉过正。
  • 有一个允许它的 GNU 扩展,但它不是标准 C。在接受的答案here 上查看 cmets。
  • @alex 有趣!我相信,这几乎就是 OP 所要求的。另一个开源比喻的例子是,如果你对一个用例有合理的需求,很可能有人已经这样做了......

标签: c format-specifiers


【解决方案1】:

这在标准 C 中是不可能的。

我建议创建一个将结构作为参数的函数,并在其主体中打印结构的所有字段。

然后代替:

printf("%q",x);   // Where x is a data type variable.

你可以使用你的函数来打印,像这样:

print_struct(x);

Print the structure fields and values in CUsing functions to print structures 中了解更多信息。


如果你想打印结构的所有字段,那么我建议阅读这个Run through a structure and print all the values?

【讨论】:

  • 非常感谢。我肯定会尝试这个,但不会从头文件中更改代码吗?我的意思是他们可能在编辑后也有功能甚至我自己创建的功能?请帮忙。
  • 这肯定会奏效,但 OP 可能设想了类似for (i=0; i<ARRLEN; ++i) { printf("Contents of struct %3d: %q\n", i, arr[i]); }...
  • 是的@Peter-ReinstateMonica。我实际上不想修改 printf() 函数。可能吗?请帮忙。
  • @Peter-ReinstateMonica 啊,没想到。我更新了我的答案un-through-a-structure-and-print-all-the-values
  • 哦,好的,谢谢@gsamaras 的帮助。我一定会试试这个。非常感谢。
【解决方案2】:

编辑:第二个程序演示了如何按值传递结构

这是一个示例,如何为使用 gnu C 标准库的系统定义 printf 自定义转换。程序首先打印 glibc 版本以防 printf API 发生变化(就像过去一样):

#include <stdio.h>
#include <printf.h>
#include <gnu/libc-version.h> // for glibc version information

struct data
{
    int d;
    char str;
};


int
print_struct_ptr (FILE *stream,
              const struct printf_info *info,
              const void *const *args)
{
  const struct data *aStruct;
  int len;

  aStruct = *((const struct data **) (args[0]));
  /* Simply print the members to the stream */
  len = fprintf (stream, "[struct data at %p: d=%d, str=%c]\n",
        aStruct, aStruct->d, aStruct->str);
  return len;
}

int
print_struct_arginfo_sz (const struct printf_info *info, size_t n,
                      int *argtypes, int *size)
{
  /* We always take exactly one argument and this is a pointer to the
     structure.. */
  if (n > 0)
    argtypes[0] = PA_POINTER;
  return 1;
}

int main(void)
{
  struct data myStruct = {3, 'A'};
  printf("glibc version: %s\n", gnu_get_libc_version());
  register_printf_specifier('M', print_struct_ptr, print_struct_arginfo_sz);
  return printf("%M\n", &myStruct) > 0 ? 0 : -1;
}

这将打印您的数据结构。 (顺便说一句,将角色成员命名为 str 很奇怪——这应该是一个指针吗?我现在就这样了。)

首先要观察的是我将指针传递给你的结构。这使得注册自定义转换更容易,因为printf 知道如何从变量参数列表中提取指针。通过在print_struct_arginfo_sz() 中插入PA_POINTER 来指示参数是指针。此处不需要其他信息字段。

因为我们传递了一个内置类型,我们只需要定义两个相当简单的回调函数。第一个,print_struct(),确实打印了传递的指针指向的结构,只需通过指针访问其数据成员即可。如前所述,第二个函数通知printf() 要提取的数据类型(指针)。

最后,main() 中的 register_printf_specifier() 调用连接了这些点:它注册了这两个回调(函数指针作为参数传递),将它们与格式说明符 'M' 连接起来。

这是一个示例会话。编译器会警告“未知”转换说明符,并且由于无法识别转换,因此会警告没有转换说明符的参数。这是可以忽略此类警告的极少数情况之一。

$ gcc -Wall -o printf-customization printf-customization.c && ./printf-customization
printf-customization.c: In function ‘main’:
printf-customization.c:43:19: warning: unknown conversion type character ‘M’ in format [-Wformat=]
   return printf("%M\n", &myStruct) > 0 ? 0 : -1;
                   ^
printf-customization.c:43:17: warning: too many arguments for format [-Wformat-extra-args]
   return printf("%M\n", &myStruct) > 0 ? 0 : -1;
                 ^~~~~~
glibc version: 2.24
[struct data at 0xbf9b6448: d=3, str=A]

为了完整起见,这里是一个结构体按值传递的版本。我在正确解释传递给 print 函数的 args 参数时遇到了困难——它是指向实际参数的指针数组中的第一个元素的指针。显然,args 是一个指向指针的指针,即使对于按值传递的 printf 参数也是如此,也就是说,args[0] 仍然是一个指针,而不是一个简单的结构指针。我不确定为什么会这样。检查printf 来源可能会产生见解,但我真的缺乏时间和动力。这是完整的,又是冗长的程序。

#include <stdio.h>
#include <printf.h>
#include <string.h>
#include <gnu/libc-version.h> // for glibc version information

// We can be gcc specific here and use variadic macros.
#if DEBUG
 #define dbg(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
#else
# define dbg(fmt, ...)
#endif

#define err(...) fprintf(stderr, __VA_ARGS__)

/// Our custom type we want to print
struct T { int i; char c; };

/// Callback to extract an struct T from va_list and copy into dest
void getTFromVaList(void *dest, va_list *valist)
{
  struct T t = va_arg(*valist, struct T);
  // debug: This looks good.
  dbg("extracted struct T (%d, %c) to mem %p\n", t.i, t.c, dest);
  memcpy(dest, &t, sizeof(t));
}

/// Global variable to communicate conversion type,
/// will be set in main before printf call
static int TArgType;

/// Callback that prints arguments pointed to by pointers
/// in the args array
int printT (FILE *stream,
            const struct printf_info *info,
            const void *const *args)
{
  struct T *tPtr = **(struct T ***)args;
  int len;

  len = fprintf (stream, "[struct T at %p: t.i=%d, t.c=%c]",
                 tPtr, tPtr->i, tPtr->c);
  if(len <= 0) { err("fprintf to stream failed\n"); }
  return len;
}

/// set the argument type connected to specifier T
int fillTArgType( const struct printf_info *info,
                  size_t n,
                  int *argtypes,
                  int *size )
{
  // debug: Looks good
  dbg("printf_info spec: %c, struct T arg type=%d\n", (char)info->spec, TArgType);
  if (n != 1) { err("Weird: n != 1\n"); }

  argtypes[0] = TArgType;
  *size = sizeof(struct T);

  // return the number of arguments required for T, which is 1.
  return 1;
}

int main(void)
{
  struct T t = { 23, 'A'};
  printf("glibc version: %s\n", gnu_get_libc_version());

  // Set global variable used in fillTArgType
  TArgType = register_printf_type(getTFromVaList);
  dbg("T arg type: %d\n", TArgType);
  // register specifier
  register_printf_specifier('T', printT, fillTArgType);

  // try it out
  int diag = printf("%T\n", t);

  return diag > 0 ? 0 : -1; // 0 bytes printed is failure, too.
}

示例会话:

$ gcc -DDEBUG -Wall -o printf-custom-struct printf-custom-struct.c && ./printf-custom-struct && echo $?
printf-custom-struct.c: In function ‘main’:
printf-custom-struct.c:75:23: warning: unknown conversion type character ‘T’ in format [-Wformat=]
   int diag = printf("%T\n", t);
                       ^
printf-custom-struct.c:75:21: warning: too many arguments for format [-Wformat-extra-args]
   int diag = printf("%T\n", t);
                     ^~~~~~
glibc version: 2.24
T arg type: 8
printf_info spec: T, struct T arg type=8
extracted struct T (23, A) to mem 0xbfabfdb0
[struct T at 0xbfabfdb0: t.i=23, t.c=A]
0

【讨论】:

  • 非常感谢@Peter - 恢复莫妮卡,这真的很有帮助。这真的意味着很多。谢谢你。也非常感谢 alex01011 之前提到这一点。
  • @SuryaMajumder 考虑接受彼得的回答,因为我相信它回答了超出标准 C 范围的问题。 :)
  • 好的,谢谢@gsamaras。非常感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-19
相关资源
最近更新 更多