2009-10-13 63 views
7

我正在使用va_list構造呈現的字符串。存儲va_list以備後用於C/C++的最佳方式

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

這一切都很好,但現在用戶有能力在應用程序運行時更改語言。我需要重新生成所有文本字符串並在初始化後重新緩存文本位圖。我想存儲va_list並在需要生成文本時使用它。

爲了給你更多的背景知識,這需要發生在我正在翻譯的關鍵字串中有一段動態數據的情況下。

"Player Score:%d" 

這是我需要翻譯的關鍵字符串。我想保留va_list中提供的數字,以備後面使用(在初始化文本的函數的範圍之外),以便在初始化之後需要重新翻譯它。最好我想持有vsnprintf的va_list副本。

我已經做了一些研究,並已經找到了一些方法。其中一些我質疑它是否是一種合適的方法(就穩定性和便攜性而言)。

+2

你能提供一個更好的描述你的意思是「稍後」嗎?我認爲任何人都應該很清楚,只要相應的可變參數函數調用仍然有效(即從低層實現相關的角度來看,只要相應的棧幀可用),「va_list」參數是活着的)。在函數返回之後,任何嘗試訪問它都是災難的祕訣。 – AnT 2009-10-13 21:45:21

+1

改爲使用Boost.Format來構建字符串。如果你能避免它,沒有理由炸燬類型安全。 – jalf 2009-10-14 19:08:59

+0

是的,我們最終會做類似的事情,因爲這個方法(問題中的那個)只是普通的缺陷。 – resolveaswontfix 2009-10-14 19:50:50

回答

6

存儲va_list本身不是一個好主意;該標準僅要求va_list參數適用於va_start(),va_arg()va_end()。據我所知,va_list不保證是可複製的。

但是,您不需要存儲va_list。將提供的參數複製到另一個數據結構中,如vector(void *,可能),然後以通常的方式檢索它們。你需要注意類型,但是對於C++中的printf風格的函數來說,情況總是如此。

+0

我一直想知道va_list的可複製/可分配的質量。我似乎無法在標準中找到任何討論。 – 2009-10-13 21:35:06

+0

這將是因爲它是具體實現我猜。我知道一些CPU將前幾個參數存儲在寄存器中而不是堆棧中,這會使複製va_list變得有點棘手。另外,va_list不知道有多少個參數。您確實需要一個具有數量信息,簡單隨機訪問和定義明確的複製語義的結構。有些語言可能需要按不同的順序進行參數設置! – Skizz 2009-10-13 21:47:25

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文件。)
  • 使用此緩存的內存緩衝區而不是由va_start()創建的指針調用vsnprintf()

上述項目在大多數平臺上都可行,包括Linux和Windows。

您可能希望考慮翻譯的項目是詞序關注。什麼是用英文寫成的:

球員山姆得了20分。

可能在一些(人)語言只能用流利的詞序analagous寫到:

20點已經由球員薩姆進球。

出於這個原因,在Win32​​API使用printf()樣格式字符串,與該參數被編號,如在功能上的差異:

玩家%1已經打進%2 d!點。
%2!d!積分已經被玩家%1打分。

(string類型假定爲每個參數,因此%1相當於%1!s!

你可以不使用Win32 API當然,但改變的格式化參數的詞序的功能正是我想要作爲一個概念介紹的。你也可以在你的軟件中實現它。

+0

你在那裏提出了一個完全有效的觀點,我應該簡單地提供一個例子,在源字符串中只有一個%。 va_list標準的格式也是如此,以便我可以將創建的緩衝區(通過cache_arguments)發送到vsprintf。只是想確保我正確理解你。這需要在Windows,Linux和Mac上運行。如果沒有,我只需要製作自己的printf,它需要一個我存儲的值的向量,而不是...。 – resolveaswontfix 2009-10-14 04:36:56

+0

va_list的實現不是標準的,但是從va_arg()返回的值是。要調整所需的內存大小,請通過參數進行「試運行」,而不必實際讀取它們的值。使用va_arg()返回它們的地址。您將需要額外一次調用va_arg()來檢索虛構的最終字符。通過從初始值中減去va_arg()的最終返回值,您可以獲得所需的字節數。分配該字節數+ sizeof(int)。對參數進行第二遍處理,現在將它們編碼到已分配的緩衝區中 - 從字節開始_sizeof(int)_ – 2009-10-14 05:30:42

+0

啊,是的...由於需要實現va_start/va_arg的方式(返回指針的內容),在所有平臺上使用&va_arg(ap)是合法的 - 這是獲取參數地址所必需的。 – 2009-10-14 05:34:52

7

這個問題真的激起了我的興趣。另外我在自己的工作中也會面臨類似的問題,所以這裏設計的解決方案也可能對我有所幫助。

簡而言之,我編寫了概念驗證代碼,它可以緩存可變參數供以後使用 - 您可以在下面找到它。

我能夠得到下面的代碼,以在Windows和基於Linux的Linux上正常工作。我使用Linux上的gcc和Windows上的MSVC編譯。關於濫用gcc的va_start()有兩次重複的警告 - 警告您可以在makefile中禁用該警告。

我很想知道這段代碼是否適用於Mac編譯器。編譯它可能需要稍微調整一下。

我知道這個代碼是:

  • 極端在其濫用的va_start的()作爲由ANSI C標準定義。
  • Old-school byte-oriented 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 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; 

} 
3

您可以使用va_copy(),這裏有一個例子:

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

問題中的關鍵字是「以後使用」。函數返回後,tmp和ap變量都不能使用。 – kaspersky 2013-07-29 13:00:31

0

做在C的方法是的參數的結構發送到函數。您應該通過引用傳遞結構,然後將結構複製(memcpy)到一個公共位置,這將允許您稍後重用它。您可以使用與發送方式相同的方式解開目標結構。您保留「設置和獲取」結構的模板。