2009-10-13 6 views
7

Używam va_list do skonstruowania napisu, który jest renderowany.Najlepszy sposób na przechowywanie va_list do późniejszego użycia w C/C++

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

Wszystko jest w porządku i dobrze, ale teraz użytkownik ma możliwość zmiany języka podczas działania aplikacji. Muszę zregenerować wszystkie ciągi tekstowe i ponownie buforować bitmapy tekstowe po inicjalizacji. Chciałbym przechowywać va_list i używać go, ilekroć trzeba wygenerować tekst.

Aby uzyskać więcej informacji, musi się to zdarzyć w przypadku, gdy ciąg klucza, który tłumaczę, zawiera w sobie dynamiczny fragment danych.

"Player Score:%d" 

To jest kluczowy ciąg, który muszę przetłumaczyć. Chciałbym trzymać liczby podane na liście va_list do późniejszego użycia (poza zakresem funkcji, która inicjalizuje tekst) w przypadku, gdy musi zostać przetłumaczone ponownie po inicjalizacji. Najlepiej chciałbym mieć kopię va_list do użycia z vsnprintf.

Zrobiłem kilka badań na ten temat i znalazłem kilka sposobów. Niektóre z nich kwestionuję, czy jest to odpowiednia metoda (pod względem stabilności i przenośności).

+2

Czy możesz podać dokładniejszy opis tego, co masz na myśli przez "później"? Myślę, że dla każdego powinno być jasne, że "va_list" może być poprawna tylko wtedy, gdy odpowiednie wywołanie funkcji variadic jest nadal aktywne (tj. Z niskiego poziomu, zależnego od implementacji punktu widzenia, o ile odpowiadająca ramka stosu z parametrami żyje). Każda próba dostępu do niej po powrocie funkcji jest receptą na katastrofę. – AnT

+1

użyj Boost.Format zamiast budować ciąg. Nie ma powodu, by wysadzać w powietrze bezpieczeństwo typu, jeśli możesz tego uniknąć. – jalf

+0

Tak, skończymy robić coś podobnego do tego, ponieważ ta metoda (ta w pytaniu) jest po prostu błędna. – resolveaswontfix

Odpowiedz

6

Przechowywanie samego va_list nie jest świetnym pomysłem; standard wymaga tylko, aby argument va_list działał z va_start(),i . O ile mogę powiedzieć, va_list nie ma gwarancji, że można go skopiować.

Ale nie trzeba przechowywać va_list. Skopiuj dostarczone argumenty do innej struktury danych, na przykład wektora (prawdopodobnie void *), i pobierz je później w zwykły sposób. Musisz uważać na typy, ale tak jest zawsze w przypadku funkcji stylu printf w C++.

+0

Zastanawiam się nad możliwymi do skopiowania i przypisania właściwościami va_list. Nie mogę znaleźć żadnej dyskusji na ten temat w standardzie. –

+0

To dlatego, że jest to specyficzne dla implementacji. Znam niektóre procesory przechowujące kilka pierwszych argumentów w rejestrach zamiast stosu, co spowodowałoby, że kopiowanie va_list było nieco trudniejsze. Ponadto va_list nie wie ile jest argumentów. Naprawdę potrzebujesz struktury, która zawiera informacje o ilości, prosty losowy dostęp i dobrze zdefiniowaną semantykę kopiowania. Niektóre języki mogą wymagać argumentów w innej kolejności! – Skizz

2

To, co opisujesz na temat "trzymania numerów podanych na va_list", jest sposobem podejścia do tego.

Urządzenie va_list utrzymuje wskaźniki do pamięci tymczasowej na stosie (tak zwane "automatyczne przechowywanie" w standardzie C). Po powrocie funkcji ze zmiennymi args, automatyczne przechowywanie zniknęło i zawartość nie jest już użyteczna. Z tego powodu nie można po prostu zachować kopii samego pliku va_list - pamięć, do której się odwołuje, będzie zawierała nieprzewidywalną treść.

W podanym przykładzie należy przechowywać dwie liczby całkowite, które są ponownie używane podczas ponownego tworzenia wiadomości. W zależności od tego, ile różnych ciągów formatów masz do czynienia, twoje podejście może się różnić.

Dla całkowicie ogólnego rodzaju podejścia, trzeba będzie:

  • Napisz funkcję „cache_arguments()”, który tworzy bufor dynamicznie pamięci wartości zmiennych występujących w argumentach.
  • Ten cache_arguments() użyłby -Style ciąg formatu printf() wraz z va_start, va_arg, va_end makr. Będziesz musiał pobrać typy zgodnie ze specyfikacjami typu printf(), ponieważ sizeof(double) != sizeof(int).
  • Przechowuj argumenty w pamięci podręcznej pamięci z tym samym wyrównaniem i dopełnieniem spodziewanym przez va_arg() na twojej platformie.(Przeczytaj swój plik varargs.h).
  • Uzyskaj połączenia z vsnprintf() współpracującymi z buforowanym buforem pamięci zamiast wskaźnika utworzonego przez va_start().

Powyższe elementy są dostępne na większości platform, w tym Linux i Windows.

Przedmiotem zainteresowania tłumaczeniem jest obyczaj zamówień. Co jest napisane w języku angielskim jako:

Gracz Sam zdobył 20 punktów.

można w niektórych językach (ludzkich) można zapisać tylko płynnie ze słowem zamówienia analogiczna do:

20 punktów zostały drużyny gracza Sam.

Z tego powodu, Win32 API FormatMessage() wykorzystuje ciąg formatu printf() -like, z tą różnicą, że parametry funkcjonalnej są ponumerowane, jak w:

Gracz% 1 zdobył 2% d! zwrotnica.
% 2! D! punkty zostały przyznane przez gracza% 1.

(typ ciąg zakłada się dla każdego argumentu, więc %1 jest równoważna %1!s!)

Oczywiście, że nie może być z wykorzystaniem Win32 API, ale funkcjonalność zmieniając kolejność słowo sformatowanych argumentów jest tym, co próbuję wprowadzić jako koncepcję. Możesz również zaimplementować to w swoim oprogramowaniu.

+0

Pod koniec wprowadzasz całkowicie poprawny punkt, powinienem podać przykład z tylko jednym% w ciągu źródłowym. Tak samo jest z formatem va_list po uruchomieniu go, aby móc wysłać bufor utworzony przez niego (poprzez argumenty cache_arguments) do vsprintf. Po prostu chcę się upewnić, że rozumiem cię poprawnie. To musi działać w systemach Windows, Linux i Mac. Jeśli nie, będę musiał zrobić własny printf, który pobiera wektor wartości, które przechowuję, w przeciwieństwie do .... – resolveaswontfix

+0

Implementacja va_list nie jest standardowa, ale wartość zwrócona przez va_arg() jest. Aby zmienić wymaganą pamięć, wykonaj "próbne uruchomienie" przez argumenty bez faktycznego odczytania ich wartości. Użyj va_arg(), aby zwrócić swoje adresy. Będziesz musiał zadzwonić va_arg() jeden dodatkowy czas, aby pobrać wyimaginowany końcowy znak. Odejmując końcową wartość zwracaną przez va_arg() od początkowej, można uzyskać liczbę wymaganych bajtów. Przydziel tę liczbę bajtów + sizeof (int). Wykonaj drugie przejście przez argumenty, teraz kompilując je do przydzielonego bufora - zacznij od bajtu _sizeof (int) _ –

+0

Ah, tak ... ze względu na sposób, w jaki va_start/va_arg muszą być zaimplementowane (zwracanie zawartości wskaźnika), będzie legalne używanie & va_arg (ap) na wszystkich platformach - która jest wymagana do uzyskania adresów parametrów. –

7

To pytanie naprawdę wzbudziło moje zainteresowanie. Również będę musiał zmierzyć się z podobnym problemem w mojej pracy, więc rozwiązanie opracowane tutaj może mi również pomóc.

W skrócie, napisałem kod typu proof-of-concept, który buforuje zmienne argumenty do późniejszego wykorzystania - można go znaleźć poniżej.

Udało mi się uzyskać poniższy kod, aby działał poprawnie zarówno w systemie Windows, jak i w systemie Linux opartym na inteligencji. Kompilowałem z gcc na Linux i MSVC na Windows. Istnieje dwa razy powtarzane ostrzeżenie o nadużywaniu va_start() z gcc - które ostrzega, że ​​można wyłączyć w makefile.

Chciałbym wiedzieć, czy ten kod działa na kompilatorze Mac. Może to zająć trochę zmian, aby go skompilować.

Zdaję sobie sprawę, ten kod jest:

  • skrajności jego nadużywania va_start(), jak określono w normie ANSI C.
  • Old-school zorientowany na bajt C.
  • Teoretycznie nieprzenośny w użyciu zmiennej va_list jako wskaźnika.

Moje użycie funkcji malloc() i free() było bardzo celowe, ponieważ makra va_list pochodzą ze standardu C i nie są funkcjami C++.Rozumiem, że twój tytuł pytania wymienia C++, ale spróbowałem stworzyć rozwiązanie w pełni kompatybilne z C, inne niż użycie komentarzy w stylu C++.

Ten kod niewątpliwie zawiera pewne błędy lub nieprzydatności w przetwarzaniu ciągu formatów. Podaję to jako dowód koncepcji, którą zhakowałem w ciągu dwóch godzin, a nie gotową próbkę kodu gotową do użytku profesjonalnego.

To oświadczenie odrzuca, mam nadzieję, że wynik jest tak wspaniały, jak ja! To było cudowne pytanie, w które można się włamywać. Chory i pokręcony charakter tego wyniku wywołuje głęboki śmiech. ;)

 
#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

Można użyć va_copy(), oto przykład:

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

Słowo kluczowe w pytaniu to "późniejszy użytek". Po powrocie funkcji nie można używać zmiennych tmp i ap. – kaspersky

0

Sposobem na to zrobić w C jest wysłanie struct argumentów do funkcji zamiast. Powinieneś przekazać strukturę przez odwołanie, a następnie skopiować (memcpy) strukturę do wspólnej lokalizacji, która pozwoli ci na ponowne jej użycie później. Rozplącz strukturę docelową w ten sam sposób, w jaki ją wysłałeś. Zachowujesz szablon struktury dla "ustawiania i pobierania".

Powiązane problemy