Повторное использование va_list - PullRequest
7 голосов
/ 18 февраля 2010

Мне нужно сделать два (или более) прохода за va_list. У меня есть буфер некоторого размера, и я хочу записать в него отформатированную строку со sprintf. Если отформатированная строка не помещается в выделенное пространство, я хочу удвоить выделенное пространство и повторять до тех пор, пока она не уместится.

(В качестве примечания я хотел бы сначала вычислить длину отформатированной строки и выделить достаточно места, но единственная найденная мной функция, которая может это сделать, - _snprintf, и она устарела в VS2005. ..)

Теперь пока проблем нет: я использую vsnprintf и звоню va_start перед каждым вызовом.

Но я также создал функцию, которая принимает в качестве параметра va_list вместо "...". Тогда я не смогу снова использовать va_start! Я читал о va_copy, но он не поддерживается в VS2005.

Итак, как бы вы это сделали?

Ответы [ 5 ]

6 голосов
/ 18 февраля 2010

В предыдущем вопросе об отсутствии va_copy в MSVC было несколько достаточно приличных предложений, в том числе для реализации собственной версии va_copy для использования в MSVC:

#define va_copy(d,s) ((d) = (s))

Возможно, вы захотите добавить это в заголовок «переносимости», защищенный #ifndef va_copy и #ifdef _MSC_VER для использования в VC.

4 голосов
/ 17 мая 2012

Предполагается, что va_copy () является частью спецификации C99; как и для всех переменных, поддержка параметров очень зависит от платформы. Макросы va_list, va_copy (), va_start (), va_end () определены в stdarg.h .

GCC: при попытке повторно использовать va_list в GCC, один ДОЛЖЕН использовать va_copy (), так как реализация GCC вызывает изменение va_list, в результате чего указатель позиционируется после последнего параметра после использования av ?? printf () функция.

СОЛНЦЕ: При попытке повторно использовать va_list в SunStudio (v11, v12) переменная va_list не затрагивается и может использоваться повторно столько раз, сколько необходимо, без необходимости va_copy ().

MS_Visual C: Не уверен, но похоже, что в документах VC ++ 2010 года упоминается 'va_copy ()' и может подразумеваться, что повторное использование va_list является разумным, но должно быть проверено.

Пример:

#include <stdio.h>
#include <stdarg.h>
/**
 * Version of vsprintf that dynamically resizes the given buffer to avoid overrun.
 * Uses va_copy() under GCC compile.
 **/    
int my_vsprintf(char **buffer, char *msg, va_list args)
{
   int bufLen = 0;
   va_list dupArgs;       // localize args copy

#ifdef __GNUC__
   va_copy(dupArgs,args); // Clone arguments for reuse by different call to vsprintf.
#else 
   dupArgs = args;        // Simply ptr copy for GCC compatibility
#endif

   // Perform 1st pass to calculate required buffer size.  The vsnprintf() funct
   // returns the number of chars (excluding \0 term) necessary to produce the output.
   // Notice the NULL pointer, and zero length.
   bufLen = vsnprintf(NULL,0,msg, dupArgs); 

   // NOTE: dupArgs on GCC platform is mangled by usage in v*printf() and cannot be reused.
#ifdef __GNUC__
   va_end(dupArgs); // cleanup 
#endif

   *buffer = realloc(*buffer,bufLen + 1);  // resize buffer, with \0 term included.

#ifdef __GNUC__
   va_copy(dupArgs,args); // Clone arguments for reused by different call to vsprintf.
#endif

   // Perform 2nd pass to populate buffer that is sufficient in size,
   // with \0 term size included.
   bufLen = vsnprintf(buffer, bufLen+1, msg, dupArgs);

   // NOTE: dupArgs on GCC platform is mangled by usage in v*printf() and cannot be reused.
#ifdef __GNUC__
   va_end(dupArgs); // cleanup
#endif

   return(bufLen);  // return chars written to buffer.
}

/**
 * Version of sprintf that dynamically resizes the given buffer to avoid buffer overrun
 * by simply calling my_vsprintf() with the va_list of arguments.
 *
 * USage:
 **/
int my_sprintf(char **buffer, char *msg, ...)
{
    int bufLen = 0;
    va_list myArgs; 

    va_start(myArgs, msg);   // Initialize myArgs to first variadic parameter.

    // re-use function that takes va_list of arguments.       
    bufLen = my_vsprintf(buffer, msg, myArgs ); 

    va_end(myArgs);
} 
3 голосов
/ 21 февраля 2015

Поздно ответить, но, надеюсь, кто-то найдет это полезным. Мне нужно было использовать va_copy, но, будучи недоступным в vc2005, я искал и оказался на этой странице.

Я был немного обеспокоен использованием, казалось бы, грубой:

va_copy(d,s) ((d) = (s))

Так что я немного покопался. Я хотел посмотреть, как va_copy был реализован в vc2013, поэтому я скомпилировал тестовую программу, которая использует va_copy, и разобрал ее:

Во-первых, его использование в соответствии с MSDN:

void va_copy(
   va_list dest,
   va_list src
); // (ISO C99 and later)

разборка va_copy, реализованная в msvcr120 (всего 7 строк!):

PUSH EBP
MOV EBP,ESP
MOV EAX,DWORD PTR SS:[EBP+8]  ;get address of dest
MOV ECX,DWORD PTR SS:[EBP+0C] ;get address of src
MOV DWORD PTR DS:[EAX],ECX    ;move address of src to dest
POP EBP
RETN

Итак, как вы можете видеть, это на самом деле так же просто, как присвоить значение src va_list для dest va_list перед анализом.

Поскольку я использую только MS-компиляторы и в настоящее время использую vc2005 с возможностью обновления в будущем, вот что я сделал:

#if _MSC_VER < 1800 // va_copy is available in vc2013 and onwards
#define va_copy(a,b) (a = b)
#endif
1 голос
/ 18 февраля 2010

Я не вижу никакого портативного способа (и я думаю, что va_copy был введен в C99, потому что не было никакого портативного способа достичь его результата в c89). Va_list может быть макетом ссылочного типа, объявленным как

typedef struct __va_list va_list[1];

(см. Gmp для другого пользователя этого трюка), и это объясняет множество языковых ограничений вокруг них. Кстати, не забудьте va_end, если важна переносимость.

Если переносимость не важна, я проверю stdard.h и посмотрю, смогу ли я что-нибудь взломать, учитывая истинное объявление.

0 голосов
/ 28 января 2017

Хммм, столкнулся с этим вопросом, пытаясь ответить на другой вопрос, и подумал, что я обновлю его какой-то с трудом завоеванной мудростью эпохи VS2015.

std :: строковые и переменные шаблоны

template<typename ... Args>
std::string format(const std::string& fmt, Args ... args)
{
    size_t size = std::snprintf(nullptr, 0, fmt.c_str(), args ...) + 1; // Extra space for '\0'
    char *cbuf = std::make_unique<char[]>(size).get();
    std::snprintf(cbuf, size, fmt.c_str(), args ...);
    return std::string(cbuf, cbuf + size - 1); // We don't want the '\0' inside our string tho
}

// usage:
::OutputDebugStringA(format("0x%012llx", size).c_str());

старый курс обучения

// The macros defined in STDARG.H conform to the ISO C99 standard; 
// the macros defined in VARARGS.H are deprecated but are retained for 
// backward compatibility with code that was written before the ANSI C89 
// standard.

#include <stdio.h>  
#include <stdarg.h>  

static void TraceA(LPCSTR format, ...) {

    // (*) va_copy makes a copy of a list of arguments in its current state.
    // The src parameter must already be initialized with va_start; it may
    // have been updated with va_arg calls, but must not have been reset 
    // with va_end. The next argument that's retrieved by va_arg from dest 
    // is the same as the next argument that's retrieved from src.

    // (*) The vsnprintf function returns the number of characters 
    // written, not counting the terminating null character. If the 
    // buffer size specified by count is not sufficiently large to 
    // contain the output specified by format and argptr, the return 
    // value of vsnprintf is the number of characters that would be 
    // written, not counting the null character, if count were 
    // sufficiently large. If the return value is greater than count 
    // - 1, the output has been truncated. A return value of -1 
    // indicates that an encoding error has occurred.

    va_list args, argsCopy;
    va_start(args, format);
    va_copy(argsCopy, args);

    // +1 for trailing \0 +1 for \n
    size_t size = vsnprintf(nullptr, 0, format, args) + 1; 

    va_end(args);

    auto buf = std::make_unique<char[]>(size);

    size_t written = vsnprintf(buf.get(), size, format, argsCopy);
    va_end(args);

    // Do what you will with the result
    char *buffer = buf.get();
}
...