这个问题真的引起了我的兴趣。此外,我在自己的工作中也会遇到类似的问题,所以这里设计的解决方案也可能对我有帮助。
简而言之,我编写了概念验证代码,它缓存了变量参数以供以后使用——你可以在下面找到它。
我能够让以下代码在 Windows 和基于英特尔的 Linux 上正常工作。我在 Linux 上用 gcc 编译,在 Windows 上用 MSVC 编译。有一个关于从 gcc 滥用 va_start() 的重复警告 - 您可以在 makefile 中禁用该警告。
我很想知道这段代码是否适用于 Mac 编译器。编译它可能需要一些调整。
我意识到这段代码是:
- 极端滥用 ANSI C 标准定义的 va_start()。
- 老式的面向字节的 C.
- 将 va_list 变量用作指针在理论上是不可移植的。
我对 malloc() 和 free() 的使用是经过深思熟虑的,因为 va_list 宏来自 C 标准,而不是 C++ 特性。我意识到您的问题标题提到了 C++,但除了使用一些 C++ 风格的注释之外,我还试图产生一个完全与 C 兼容的解决方案。
这段代码无疑在格式字符串处理中也存在一些错误或不可移植性。我提供这个作为我在两个小时内一起破解的概念证明,而不是准备好供专业使用的完整代码示例。
那个免责声明说,我希望你发现结果和我一样令人愉快!这是一个很好的问题。结果病态和扭曲的性质让我大笑起来。;)
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#define 详细 0
#ifdef 窗口
#define strdup _strdup
#万一
/*
* 结构 cached_printf_args
*
* 这用作动态分配的指针类型
* 保存变量参数副本的内存。结构
* 以 const char * 开头,它接收 printf() 的副本
* 格式字符串。
*
* 以零长度数组结束结构的目的是
* 允许数组名作为后面数据的符号
*那个结构。在这种情况下,额外的内存将始终
* 分配给实际包含变量 args 和 cached_printf_args->args
* 将命名该额外缓冲区空间的起始地址。
*
*/
结构 cached_printf_args
{
常量字符 * fmt;
字符参数[0];
};
/*
* copy_va_args -- 接受 printf() 格式字符串和 va_list
* 论据。
*
* 在 *p_arg_src 中推进 va_list 指针
* 符合格式字符串中的规范。
*
* 如果提供的 arg_dest 不为 NULL,则每个参数
* 根据 *p_arg_src 复制到 arg_dest
* 到格式字符串。
*
*/
int copy_va_args(const char * fmt, va_list * p_arg_src, va_list arg_dest)
{
常量字符 * pch = fmt;
int processing_format = 0;
而 (*pch)
{
如果(处理格式)
{
开关 (*pch)
{
//case '!': 在某些实现中可能是合法的,例如 FormatMessage()
案例“0”:
情况1':
案例“2”:
案例“3”:
案例“4”:
案例“5”:
案例“6”:
案例“7”:
案例“8”:
案例“9”:
案子 '。':
案子 '-':
// 以上所有字符在 % 和类型说明符之间都是合法的。
// 由于对缓存参数没有任何影响,这里它们只是
// 忽略。
休息;
案例“l”:
案例“我”:
案例“h”:
printf("还不支持大小前缀。\n");
退出(1);
案例“c”:
案例“C”:
// char 在通过 '...' 时被提升为 int
案例“x”:
案例“X”:
案例“d”:
案例“我”:
案例“o”:
案例“你”:
如果(arg_dest)
{
*((int *)arg_dest) = va_arg(*p_arg_src, int);
va_arg(arg_dest, int);
}
别的
va_arg(*p_arg_src, int);
#如果详细
printf("va_arg(int), ap = %08X, &fmt = %08X\n", *p_arg_src, &fmt);
#万一
处理格式= 0;
休息;
案例's':
案例“S”:
案例“n”:
案例“p”:
如果(arg_dest)
{
*((char **)arg_dest) = va_arg(*p_arg_src, char *);
va_arg(arg_dest, 字符 *);
}
别的
va_arg(*p_arg_src, 字符 *);
#如果详细
printf("va_arg(char *), ap = %08X, &fmt = %08X\n", *p_arg_src, &fmt);
#万一
处理格式= 0;
休息;
案例“e”:
案例“E”:
案例“f”:
案例“F”:
案例“g”:
案例“G”:
案例“一”:
案例“A”:
如果(arg_dest)
{
*((双 *)arg_dest) = va_arg(*p_arg_src, 双);
va_arg(arg_dest,双);
}
别的
va_arg(*p_arg_src, 双);
#如果详细
printf("va_arg(double), ap = %08X, &fmt = %08X\n", *p_arg_src, &fmt);
#万一
处理格式= 0;
休息;
}
}
否则 if ('%' == *pch)
{
if (*(pch+1) == '%')
pch++;
别的
处理格式 = 1;
}
pch++;
}
返回0;
}
/*
* printf_later -- 接受 printf() 格式字符串和变量
* 论据。
*
* 返回 NULL 或指向结构的指针
* 稍后与 va_XXX() 宏一起使用来检索
* 缓存的参数。
*
* 调用者必须释放()返回的结构以及
* 其中的 fmt 成员。
*
*/
结构 cached_printf_args * printf_later(const char *fmt, ...)
{
struct cached_printf_args * 缓存;
va_list ap;
va_list ap_dest;
字符 * buf_begin, *buf_end;
诠释 buf_len;
va_start(ap, fmt);
#如果详细
printf("va_start, ap = %08X, &fmt = %08X\n", ap, &fmt);
#万一
buf_begin = (char *)ap;
// 使用 NULL 目标进行“复制”调用。这推进
// 源点并允许我们计算所需的
// 缓存缓冲区大小。
copy_va_args(fmt, &ap, NULL);
buf_end = (char *)ap;
va_end(ap);
// 计算参数所需的字节数:
buf_len = buf_end - buf_begin;
如果(buf_len)
{
// 添加将用于伪造的“header”字节
// 向上最后一个非变量参数。一个指针
// 无论如何都需要格式字符串的副本,因为
// 稍后解包参数需要我们记住
// 它们是什么类型。
buf_len += sizeof(struct cached_printf_args);
缓存 = malloc(buf_len);
如果(缓存)
{
memset(缓存,0,buf_len);
va_start(ap, fmt);
va_start(ap_dest, 缓存->fmt);
// 实际上将参数从我们的堆栈复制到缓冲区
copy_va_args(fmt, &ap, ap_dest);
va_end(ap);
va_end(ap_dest);
// 分配格式字符串的副本
缓存->fmt = strdup(fmt);
// 如果分配字符串失败,反向分配和
// 指针
如果 (!cache->fmt)
{
免费(缓存);
缓存=空;
}
}
}
返回缓存;
}
/*
* free_printf_cache - 释放缓存和任何动态成员
*
*/
void free_printf_cache(结构 cached_printf_args * 缓存)
{
如果(缓存)
免费((字符*)缓存-> fmt);
免费(缓存);
}
/*
* print_from_cache -- 使用存储在
* 分配的参数缓存
*
*
* 为了在 gcc 上编译,这个函数必须声明为
* 接受可变参数。否则,使用 va_start()
* 不允许使用宏。如果将其他参数传递给
* 这个函数,它们不会被读取。
*/
int print_from_cache(struct cached_printf_args * cache, ...)
{
va_list 参数;
va_start(arg, 缓存->fmt);
vprintf(缓存->fmt, arg);
va_end(arg);
}
int main(int argc, char *argv)
{
struct cached_printf_args * 缓存;
// 分配变量参数的缓存和格式字符串的副本。
cache = printf_later("所有 %d 个参数将在 %s fo%c 以后使用,可能在 %g 秒内。", 10, "stored", 'r', 2.2);
// 用对输出的一些评论来展示时间线。
printf("这个语句在缓存的创建和它的显示过程之间进行干预。\n"
// 这是实际显示缓存 printf 输出的调用。
print_from_cache(缓存);
// 不要忘记将动态内存返回到空闲存储区
free_printf_cache(缓存);
返回0;
}