Разное поведение vs(n)printf в Win и Lin
От: Tujh Голландия  
Дата: 29.10.14 11:13
Оценка:
Здравствуйте.

Подскажите пожалуйста, где я туплю.
Имеется следующий код, вроде стандартный, выдернут из статьи и прекрасно работающий под Windows (но с тем условием, что использую _vscprintf для вычисления length).
void foo( const char format[], ... )
{
    va_list args;
    size_t  length = 0;

    va_start( args, format );

    length = vsnprintf( 0, 0, format, args );

    ++length;
    char *pbuffer = new char[length];

    vsprintf( pbuffer, format, args ); // тут пробовал и vsnprintf( pbuffer, length, format, args ) с тем же результатом
    printf( "%s", pbuffer );

    va_end( args );

    delete[] pbuffer;
}

int main()
{
    foo( "Hello %s World!\n", "cruel" );

    return 0;
}

При вызове vsprintf/vsnprintf получаю или SIGSEGV или мусор на месте вставки по типу "Hello ###########World!". Если вызывать просто foo( "Hello World!" ); проблем нет — строка печатается, что логично.
Подозреваю, что args указывает куда-то не туда, но в чём проблема понять не могу.
Если что:
linux 3.10 / gcc 4.8.0


Заранее благодарю за подсказку к решению этого вопросика.
Re: Разное поведение vs(n)printf в Win и Lin
От: niXman Ниоткуда https://github.com/niXman
Дата: 29.10.14 11:21
Оценка: 2 (1)
Здравствуйте, Tujh, Вы писали:

T> length = vsnprintf( 0, 0, format, args );

никогда не видел такого...

вот что говорит man:
       // To allocate a sufficiently large string and print into it (code correct for both glibc 2.0 and glibc 2.1):
       // (If truncation occurs in glibc versions prior to 2.0.6, this is treated as an error instead of being handled gracefully)

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

       char *
       make_message(const char *fmt, ...)
       {
           int n;
           int size = 100;     /* Guess we need no more than 100 bytes */
           char *p, *np;
           va_list ap;

           if ((p = malloc(size)) == NULL)
               return NULL;

           while (1) {

               /* Try to print in the allocated space */

               va_start(ap, fmt);
               n = vsnprintf(p, size, fmt, ap);
               va_end(ap);

               /* Check error code */

               if (n < 0)
                   return NULL;

               /* If that worked, return the string */

               if (n < size)
                   return p;

               /* Else try again with more space */

               size = n + 1;       /* Precisely what is needed */

               if ((np = realloc (p, size)) == NULL) {
                   free(p);
                   return NULL;
               } else {
                   p = np;
               }
           }
       }
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Отредактировано 29.10.2014 11:22 niXman . Предыдущая версия . Еще …
Отредактировано 29.10.2014 11:21 niXman . Предыдущая версия .
Re[2]: Разное поведение vs(n)printf в Win и Lin
От: niXman Ниоткуда https://github.com/niXman
Дата: 29.10.14 11:23
Оценка:
а вообще, похорошему, лучше использовать boost.format или аналоги.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re: Разное поведение vs(n)printf в Win и Lin
От: watchmaker  
Дата: 29.10.14 11:49
Оценка: 12 (3)
Здравствуйте, Tujh, Вы писали:


T>Подозреваю, что args указывает куда-то не туда,

Почти верно.
Проблема с кодом в том, что он не сбрасывает итератор args на начало. После вызова va_start итератор args указывает на первый параметр после format, после вызова vsnprintf итератор args указывает куда-то после всех съеденных параметров, и теперь второй вызов vsprintf получает указатель не на оригинальные параметры, а на память за ними.
Чтобы исправить поведение стандарт предписывает в твоём случае использовать va_copy.
Re: Разное поведение vs(n)printf в Win и Lin
От: uzhas Ниоткуда  
Дата: 29.10.14 11:52
Оценка:
Здравствуйте, Tujh, Вы писали:

T>Имеется следующий код, вроде стандартный, выдернут из статьи и прекрасно работающий под Windows (но с тем условием, что использую _vscprintf для вычисления length).


вот тут работает: http://ideone.com/ZbQ3c9

нужно больше инфы. с каким стеком падает? что именно выводит? всю строку дословно, без замен на решетки #
пример именно этот гоняете или по факту другой?
Re[2]: Разное поведение vs(n)printf в Win и Lin
От: Tujh Голландия  
Дата: 29.10.14 11:53
Оценка:
Здравствуйте, niXman, Вы писали:

X>Здравствуйте, Tujh, Вы писали:


T>> length = vsnprintf( 0, 0, format, args );

X>никогда не видел такого...
Относительно типовая конструкция, когда нужно узнать количество символов после конвертации.

If the resulting string would be longer than n-1 characters, the remaining characters are discarded and not stored, but counted for the value returned by the function.

Так как результат будет больше чем ( 0 — 1) то по нулевому указателю ни чего записано не будет, но значение длинны будет возвращено результатом выполнения функции.

X>вот что говорит man:

Видел. Но вот Вы, очередной публикацией мана, подтолкнули на мысль...и она оказалась правильной.

va_start( args, format );
length = vsnprintf( 0, 0, format, args );
va_end( args );

++length;
char *pbuffer = new cahr[length];

va_start( args, format );
vsnprintf( pbuffer, length, format, args );
va_end( args );

printf( "%s", pbuffer );

delete[] pbuffer;

вот так сработало.
А всё потому, что невнимательно прочитал, и не заметил ремарку:

Because they invoke the va_arg macro, the value of va_list is undefined after the call.

http://linux.die.net/man/3/vsnprintf

X>а вообще, похорошему, лучше использовать boost.format или аналоги.

Как раз аналог и делаю
Re[2]: Разное поведение vs(n)printf в Win и Lin
От: Tujh Голландия  
Дата: 29.10.14 11:58
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Чтобы исправить поведение стандарт предписывает в твоём случае использовать va_copy.

Как говорится, muchas gracias !
Уже разобрался, но пока до va_copy самостоятельно не докопался. Спасибо ещё раз за подсказку!
Re[2]: Разное поведение vs(n)printf в Win и Lin
От: watchmaker  
Дата: 29.10.14 12:01
Оценка:
Здравствуйте, uzhas, Вы писали:

U>Здравствуйте, Tujh, Вы писали:


T>>Имеется следующий код, вроде стандартный, выдернут из статьи и прекрасно работающий под Windows (но с тем условием, что использую _vscprintf для вычисления length).


U>вот тут работает

Неопределённое поведение тем и опасно, что иногда проявляется как «тут работает».
Например, поведение может отличаться при использовании разных целевых платформ — на i386 делает вид что работает, а на x86-64 выводит чушь.
Re[2]: Разное поведение vs(n)printf в Win и Lin
От: Tujh Голландия  
Дата: 29.10.14 12:03
Оценка:
Здравствуйте, uzhas, Вы писали:

U>нужно больше инфы. с каким стеком падает? что именно выводит? всю строку дословно, без замен на решетки #

U>пример именно этот гоняете или по факту другой?
Под решётками подразумевались произвольные символы в консоли обусловленные случайным содержимым памяти куда указывала структура args. Пример гонял именно этот. Специально написал его, для того, что бы вычленить ошибку из проекта.
Уже разобрался. в чем дело, после вызова vs(n)printf в Linux состояние args не восстанавливается и является неопределённым, в Windows состояние не меняется, вот и работает, случайно.
Вывод: хорошо, когда код проверяется сразу на нескольких платформах, находишь кучу мест неопределённого поведения.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.