存储va_list以便以后在C / C ++中使用的最佳方法

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

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

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

为了给你一些更多的背景知识,这需要在我正在翻译的关键字符串中包含动态数据的情况下发生。

 "Player Score:%d" 

这是我需要翻译的关键字符串。 我希望保留va_list中提供的数字以供以后使用(在初始化文本的函数范围之外),以便在初始化后需要重新翻译。 我希望保留一份va_list的副本,以便与vsnprintf一起使用。

我已经做了一些研究,并找到了一些方法。 其中一些我质疑它是否是一种合适的方法(在稳定和便携方面)。

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

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

这个问题确实激起了我的兴趣。 此外,我将在自己的工作中遇到类似的问题,因此这里设计的解决方案也可以帮助我。

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

我能够使以下代码在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 ++风格的注释。

毫无疑问,此代码在格式字符串处理中也存在一些错误或不可移植性。 我提供这个作为概念certificate,我在两个小时内一起入侵,而不是专业用途的完成代码示例。

免责声明说,我希望你发现结果和我一样愉快! 这是一个很难理解的问题。结果的恶劣和扭曲的性质给了我一个深深的肚子笑。 ;)

 #include 
 #include 
 #include 
 #include 

 #define VERBOSE 0

 #ifdef WINDOWS
 #define strdup _strdup
 #万一

 / *
  * struct cached_printf_args
  *
  *这用作动态分配的指针类型
  *存储器,其中包含可变参数的副本。 结构
  *以const char *开头,它收到printf()的副本
  *格式字符串。
  *
  *结束具有零长度数组的结构的目的是
  *允许数组名称作为后面数据的符号
  *那个结构。 在这种情况下,将永远存在额外的内存
  *分配实际包含变量args和cached_printf_args-> args
  *将命名该附加缓冲区空间的起始地址。
  *
  * /
 struct cached_printf_args
 {
     const char * fmt;
     char args [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)
 {
     const char * pch = fmt;

     int processing_format = 0;

    而(* pch)
     {
         if(processing_format)
         {
            开关(* 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':
            案例'你':
                 if(arg_dest)
                 {
                      *((int *)arg_dest)= va_arg(* p_arg_src,int);
                      va_arg(arg_dest,int);
                 }
                其他
                     va_arg(* p_arg_src,int);
 #if VERBOSE
                 printf(“va_arg(int),ap =%08X,&fmt =%08X \ n”,* p_arg_src,&fmt);
 #万一
                 processing_format = 0;
                打破;

            案件'':
            案件'S':
            案例'n':
            案例'p':
                 if(arg_dest)
                 {
                     *((char **)arg_dest)= va_arg(* p_arg_src,char *);
                     va_arg(arg_dest,char *);
                 }
                其他
                     va_arg(* p_arg_src,char *);
 #if VERBOSE
                 printf(“va_arg(char *),ap =%08X,&fmt =%08X \ n”,* p_arg_src,&fmt);
 #万一
                 processing_format = 0;
                打破;

            案例'e':
            案例'E':
            案例'f':
            案例'F':
            案例'g':
            案例'G':
            案例'a':
            案例'A':
                 if(arg_dest)
                 {
                     *((double *)arg_dest)= va_arg(* p_arg_src,double);
                     va_arg(arg_dest,double);
                 }
                其他
                     va_arg(* p_arg_src,double);
 #if VERBOSE
                 printf(“va_arg(double),ap =%08X,&fmt =%08X \ n”,* p_arg_src,&fmt);
 #万一
                 processing_format = 0;
                打破;
             }
         }
        否则if('%'== * pch)
         {
             if(*(pch + 1)=='%')
                 pch ++;
            其他
                 processing_format = 1;
         }
         pch ++;
     }

    返回0;
 }

 / *
  * printf_later  - 接受printf()格式的字符串和变量
  *参数。
  *
  *返回NULL或指向可以的结构的指针
  *稍后将与va_XXX()宏一起使用来检索
  *缓存的参数。
  *
  *调用者必须释放()返回的结构以及
  *其中的fmt成员。
  *
  * /
 struct cached_printf_args * printf_later(const char * fmt,...)
 {
     struct cached_printf_args * cache;
     va_list ap;
     va_list ap_dest;
     char * buf_begin,* buf_end;
     int buf_len;

     va_start(ap,fmt);
 #if VERBOSE 
     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;

     if(buf_len)
     {
         //添加将用于伪造的“标题”字节
         //最后一个非变量参数。 指向a的指针
         //无论如何都需要格式字符串的副本,因为
         //稍后解压缩参数需要我们记住
         //他们是什么类型
         buf_len + = sizeof(struct cached_printf_args);

         cache = malloc(buf_len);
         if(缓存)
         {
             memset(cache,0,buf_len);
             va_start(ap,fmt);
             va_start(ap_dest,cache-> fmt);

             //实际上将参数从我们的堆栈复制到缓冲区
             copy_va_args(fmt,&ap,ap_dest);

             va_end用来(AP);
             va_end用来(ap_dest);

             //分配格式字符串的副本
             cache-> fmt = strdup(fmt);

             //如果无法分配字符串,则反向分配和
             //指针
             if(!cache-> fmt)
             {
                自由(高速缓冲存储器);
                 cache = NULL;
             }
         }
     }

    返回缓存;
 }

 / *
  * free_printf_cache  - 释放缓存和任何动态成员
  *
  * /
 void free_printf_cache(struct cached_printf_args * cache)
 {
     if(缓存)
         free((char *)cache-> fmt);
    自由(高速缓冲存储器);
 }

 / *
  * print_from_cache  - 使用存储在中的参数调用vprintf()
  *分配参数缓存
  *
  *
  *为了在gcc上编译,必须声明此函数
  *接受变量参数。 否则,使用va_start()
  *不允许使用宏。 如果传递其他参数
  *此function,不会被阅读。
  * /
 int print_from_cache(struct cached_printf_args * cache,...)
 {
     va_list arg;

     va_start(arg,cache-> fmt);
     vprintf(cache-> fmt,arg);
     va_end用来(ARG);
 }

 int main(int argc,char * argv)
 {
     struct cached_printf_args * cache;

     //分配变量参数的缓存和格式字符串的副本。
     cache = printf_later(“这些参数的所有%d将在以后使用%s,可能在%g秒内。”,10,“存储”,“r”,2.2);

     //通过对输出的一些评论来展示时间线。
     printf(“此语句介入缓存的创建及其到显示的过程。\ n”

     //这是实际显示缓存的printf输出的调用。
     print_from_cache(高速缓冲存储器);

     //不要忘记将动态内存返回到免费商店
     free_printf_cache(高速缓冲存储器);

    返回0;

 }

你可以使用va_copy() ,这是一个例子:

 va_list ap; va_list tmp; va_copy(tmp, ap); //do something with tmp va_end(tmp); 

你所描述的“持有va_list中提供的数字”是解决这个问题的方法。

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

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

对于完全一般类型的方法,您需要:

  • 编写“ cache_arguments() ”函数,该函数创建在变量参数中找到的值的动态内存缓冲区。
  • 这个cache_arguments()将使用printf()样式的格式字符串,以及va_startva_argva_end宏。 您需要根据printf()类型说明符检索类型,因为sizeof(double) != sizeof(int)
  • 将参数存储在内存缓存中,使用平台上va_arg()所需的相同对齐和填充。 (阅读你的varargs.h文件。)
  • 使用此缓存的内存缓冲区而不是va_start()创建的指针来调用vsnprintf() va_start()

在大多数平台上都可以使用上述项目,包括Linux和Windows。

您可能希望考虑的关于翻译的项目是关于词序的问题。 什么是英文写的:

球员萨姆得到20分。

在一些(人类)语言中,只能用流利的词汇顺序写成:

球员萨姆得到20分。

因此,Win32 FormatMessage() API使用类似printf()的格式字符串,参数编号的function不同,如:

球员%1得分%2!d! 点。
%2!d! 积分已被玩家%1评分。

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

当然你可能没有使用Win32 API,但改变格式化参数的单词顺序的function是我试图作为一个概念引入的。 您也可以在软件中实现此function。

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