存储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_start
,va_arg
,va_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)复制到一个公共位置,以便稍后重用它。 您以与发送方式相同的方式解析目标上的结构。 您保留结构的模板以“设置和获取”。