9

我正在使用 va_list 来构造一个呈现的字符串。

void Text2D::SetText(const char *szText, ...)

这一切都很好,但是现在用户可以在应用程序运行时更改语言。我需要在初始化后重新生成所有文本字符串并重新缓存文本位图。我想存储 va_list 并在需要生成文本时使用它。

为了给您提供更多背景知识,这需要在我正在翻译的密钥字符串中包含动态数据的情况下发生。

"Player Score:%d"

那是我需要翻译的关键字符串。我想保留 va_list 中提供的数字以供以后使用(在初始化文本的函数范围之外),以防在初始化后需要重新翻译。我最好保留一份 va_list 以用于 vsnprintf。

我已经对此进行了一些研究,并找到了一些方法。其中一些我质疑它是否是一种合适的方法(就稳定和便携而言)。

4

5 回答 5

10

这个问题真的引起了我的兴趣。此外,我在自己的工作中也会遇到类似的问题,所以这里设计的解决方案也可能对我有帮助。

简而言之,我编写了概念验证代码,它缓存了变量参数以供以后使用——你可以在下面找到它。

我能够让以下代码在 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;

}
于 2009-10-14T09:19:06.750 回答
8

存储va_list本身不是一个好主意。该标准只要求va_list参数与va_start(),va_arg()和一起使用va_end()。据我所知,va_list不保证是可复制的。

但是您不需要存储va_list. 将提供的参数复制到另一个数据结构中,例如向量(可能是 void*),然后以通常的方式检索它们。您需要注意类型,但对于 C++ 中的 printf 样式函数,情况总是如此。

于 2009-10-13T21:32:48.313 回答
3

您可以使用va_copy(),这是一个示例:

va_list ap;
va_list tmp;
va_copy(tmp, ap);
//do something with tmp
va_end(tmp);
于 2012-06-23T11:43:14.393 回答
2

您对“保留 va_list 中提供的数字”的描述是解决此问题的方法。

维护指向堆栈上临时内存的va_list指针(C 标准中所谓的“自动存储”)。带变量 args 的函数返回后,这个自动存储就消失了,内容不再可用。因此,您不能简单地保留va_list自身的副本——它引用的内存将包含不可预测的内容。

在您给出的示例中,您将需要存储在重新创建该消息时重复使用的两个整数。根据您必须处理的不同格式字符串的数量,您的方法可能会有所不同。

对于一种完全通用的方法,您需要:

  • 编写一个“ cache_arguments()”函数,该函数创建一个动态内存缓冲区,其中包含变量参数中的值。
  • cache_arguments()将使用printf()-style 格式字符串以及va_start, va_arg,va_end宏。您将需要根据printf()类型说明符检索类型,因为sizeof(double) != sizeof(int).
  • 将参数存储在内存缓存中,并使用与va_arg()您的平台相同的对齐和填充。(阅读您的varargs.h文件。)
  • 让您vsnprintf()使用这个缓存的内存缓冲区而不是由va_start().

以上项目在大多数平台上都是可能的,包括 Linux 和 Windows。

关于翻译,您可能希望考虑的一个项目是词序问题。用英文写成:

球员山姆得了 20 分。

在某些(人类)语言中,只能用类似于以下的词序流利地书写:

玩家 Sam 得到 20 分。

出于这个原因,Win32 FormatMessage()API 使用类似printf()- 的格式字符串,其功能区别在于参数编号,如下所示:

玩家 %1 得分 %2!d! 点。
%2!d! 玩家 %1 已得分。

(每个参数都假定为字符串类型,因此%1等价于%1!s!

当然,您可能没有使用 Win32 API,但改变格式化参数的词序的功能是我试图作为一个概念引入的功能。您可能还想在您的软件中实现这一点。

于 2009-10-13T21:56:19.633 回答
0

在 C 中执行此操作的方法是向函数发送参数结构。您应该通过引用传递结构,然后将结构复制(memcpy)到一个公共位置,这样您以后就可以重用它。您以与发送结构相同的方式在目的地解开结构。您保留结构的模板以进行“设置和获取”。

于 2017-11-19T23:21:10.883 回答