我正在使用va_list构造一个呈现的字符串。
void Text2D::SetText(const char *szText, ...)
这一切都很好,但现在用户可以在应用程序运行时更改语言。我需要重新生成所有文本字符串,并在初始化后重新缓存文本位图。我想存储va_list并在需要生成文本时使用它。
为了给你更多背景知识,这需要在我正在翻译的关键字符串中包含动态数据的情况下发生。
"Player Score:%d"
这是我需要翻译的关键字符串。我希望保留va_list中提供的数字以供以后使用(在初始化文本的函数范围之外),以便在初始化后需要重新翻译。我希望保留一份va_list副本,以便与vsnprintf一起使用。
我已经做了一些研究,并找到了一些方法。其中一些我质疑它是否是一种适当的方法(在稳定和便携方面)。
答案 0 :(得分:8)
这个问题确实激起了我的兴趣。此外,我将在自己的工作中遇到类似的问题,因此这里设计的解决方案也可以帮助我。
简而言之,我编写了概念验证代码,用于缓存变量参数供以后使用 - 您可以在下面找到它。
我能够让以下代码在Windows和基于英特尔的Linux上正常运行。我在Linux上用gcc编译,在Windows上用MSVC编译。关于滥用gcc中的va_start()会有两次重复的警告 - 你可以在你的makefile中禁用这个警告。
我很想知道这段代码是否适用于Mac编译器。可能需要稍微调整一下才能编译。
我意识到这段代码是:
我对malloc()和free()的使用是非常慎重的,因为va_list宏来自C标准而不是C ++特性。我意识到你的问题标题提到了C ++,但我试图生成一个完全C兼容的解决方案,而不是使用一些C ++风格的注释。
此代码无疑在格式字符串处理中存在一些错误或不可移植性。我提供这个作为概念证明,我在两个小时内一起入侵,而不是专业用途的完整代码示例。
该免责声明说,我希望你发现结果和我一样愉快!这是一个很好的问题,可以解决这个问题。结果的恶劣和扭曲的性质给了我一个深深的肚子笑。 ;)
#include <stdio.h> #include <stdarg.h> #include <stdlib.h> #include <string.h> #define VERBOSE 0 #ifdef WINDOWS #define strdup _strdup #endif /* * struct cached_printf_args * * This is used as the pointer type of the dynamically allocated * memory which holds a copy of variable arguments. The struct * begins with a const char * which recieves a copy of the printf() * format string. * * The purpose of ending a struct with a zero-length array is to * allow the array name to be a symbol to the data which follows * that struct. In this case, additional memory will always be * allocted to actually contain the variable args, and cached_printf_args->args * will name the start address of that additional buffer space. * */ struct cached_printf_args { const char * fmt; char args[0]; }; /* * copy_va_args -- Accepts a printf() format string and va_list * arguments. * * Advances the va_list pointer in *p_arg_src in * accord with the specification in the format string. * * If arg_dest provided is not NULL, each argument * is copied from *p_arg_src to arg_dest according * to the format string. * */ int copy_va_args(const char * fmt, va_list * p_arg_src, va_list arg_dest) { const char * pch = fmt; int processing_format = 0; while (*pch) { if (processing_format) { switch (*pch) { //case '!': Could be legal in some implementations such as FormatMessage() case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.': case '-': // All the above characters are legal between the % and the type-specifier. // As the have no effect for caching the arguments, here they are simply // ignored. break; case 'l': case 'I': case 'h': printf("Size prefixes not supported yet.\n"); exit(1); case 'c': case 'C': // the char was promoted to int when passed through '...' case 'x': case 'X': case 'd': case 'i': case 'o': case 'u': if (arg_dest) { *((int *)arg_dest) = va_arg(*p_arg_src, int); va_arg(arg_dest, int); } else va_arg(*p_arg_src, int); #if VERBOSE printf("va_arg(int), ap = %08X, &fmt = %08X\n", *p_arg_src, &fmt); #endif processing_format = 0; break; case 's': case 'S': case 'n': case 'p': if (arg_dest) { *((char **)arg_dest) = va_arg(*p_arg_src, char *); va_arg(arg_dest, char *); } else va_arg(*p_arg_src, char *); #if VERBOSE printf("va_arg(char *), ap = %08X, &fmt = %08X\n", *p_arg_src, &fmt); #endif processing_format = 0; break; case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': case 'a': case 'A': if (arg_dest) { *((double *)arg_dest) = va_arg(*p_arg_src, double); va_arg(arg_dest, double); } else va_arg(*p_arg_src, double); #if VERBOSE printf("va_arg(double), ap = %08X, &fmt = %08X\n", *p_arg_src, &fmt); #endif processing_format = 0; break; } } else if ('%' == *pch) { if (*(pch+1) == '%') pch ++; else processing_format = 1; } pch ++; } return 0; } /* * printf_later -- Accepts a printf() format string and variable * arguments. * * Returns NULL or a pointer to a struct which can * later be used with va_XXX() macros to retrieve * the cached arguments. * * Caller must free() the returned struct as well as * the fmt member within it. * */ 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); #endif buf_begin = (char *)ap; // Make the 'copy' call with NULL destination. This advances // the source point and allows us to calculate the required // cache buffer size. copy_va_args(fmt, &ap, NULL); buf_end = (char *)ap; va_end(ap); // Calculate the bytes required just for the arguments: buf_len = buf_end - buf_begin; if (buf_len) { // Add in the "header" bytes which will be used to fake // up the last non-variable argument. A pointer to a // copy of the format string is needed anyway because // unpacking the arguments later requires that we remember // what type they are. buf_len += sizeof(struct cached_printf_args); cache = malloc(buf_len); if (cache) { memset(cache, 0, buf_len); va_start(ap, fmt); va_start(ap_dest, cache->fmt); // Actually copy the arguments from our stack to the buffer copy_va_args(fmt, &ap, ap_dest); va_end(ap); va_end(ap_dest); // Allocate a copy of the format string cache->fmt = strdup(fmt); // If failed to allocate the string, reverse allocations and // pointers if (!cache->fmt) { free(cache); cache = NULL; } } } return cache; } /* * free_printf_cache - frees the cache and any dynamic members * */ void free_printf_cache(struct cached_printf_args * cache) { if (cache) free((char *)cache->fmt); free(cache); } /* * print_from_cache -- calls vprintf() with arguments stored in the * allocated argument cache * * * In order to compile on gcc, this function must be declared to * accept variable arguments. Otherwise, use of the va_start() * macro is not allowed. If additional arguments are passed to * this function, they will not be read. */ 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; // Allocates a cache of the variable arguments and copy of the format string. cache = printf_later("All %d of these arguments will be %s fo%c later use, perhaps in %g seconds.", 10, "stored", 'r', 2.2); // Demonstrate the time-line with some commentary to the output. printf("This statement intervenes between creation of the cache and its journey to the display.\n" // THIS is the call which actually displays the output from the cached printf. print_from_cache(cache); // Don't forget to return dynamic memory to the free store free_printf_cache(cache); return 0; }
答案 1 :(得分:6)
存储va_list
本身并不是一个好主意;该标准仅要求va_list
参数与va_start()
,va_arg()
和va_end()
一起使用。据我所知,va_list
不能保证是可复制的。
但您不需要存储va_list
。将提供的参数复制到另一个数据结构中,例如vector(可能是void *),然后以通常的方式检索它们。您需要注意类型,但C ++中的printf样式函数总是如此。
答案 2 :(得分:3)
您可以使用va_copy()
,这是一个示例:
va_list ap;
va_list tmp;
va_copy(tmp, ap);
//do something with tmp
va_end(tmp);
答案 3 :(得分:2)
您所描述的“持有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
文件。)vsnprintf()
,而不是va_start()
创建的指针。以上项目在大多数平台上都是可能的,包括Linux和Windows。
您可能希望考虑的有关翻译的项目是关键词。什么是英文写作:
球员萨姆得了20分。
在某些(人类)语言中,只能用流利的词汇顺序书写:
球员萨姆得到20分。
出于这个原因,Win32 FormatMessage()
API使用类似printf()
的格式字符串,参数编号的功能不同,如:
玩家%1得分%2!d!点。
%2!d!球员%1得分。
(假设每个参数都使用字符串类型,因此%1
等同于%1!s!
)
当然你可能没有使用Win32 API,但改变格式化参数的单词顺序的功能是我试图作为一个概念引入的。您也可以在软件中实现此功能。
答案 4 :(得分:0)
在C中执行此操作的方法是向函数发送参数结构。您应该通过引用传递结构,然后将结构(memcpy)复制到一个公共位置,以便稍后重用它。您以与发送方式相同的方式解析目标上的结构。保留结构的模板以“设置和获取”。