Подскажите пожалуйста, где я туплю.
Имеется следующий код, вроде стандартный, выдернут из статьи и прекрасно работающий под 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
Заранее благодарю за подсказку к решению этого вопросика.
Здравствуйте, 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 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
T>Подозреваю, что args указывает куда-то не туда,
Почти верно.
Проблема с кодом в том, что он не сбрасывает итератор args на начало. После вызова va_start итератор args указывает на первый параметр после format, после вызова vsnprintf итератор args указывает куда-то после всех съеденных параметров, и теперь второй вызов vsprintf получает указатель не на оригинальные параметры, а на память за ними.
Чтобы исправить поведение стандарт предписывает в твоём случае использовать va_copy.
Здравствуйте, Tujh, Вы писали:
T>Имеется следующий код, вроде стандартный, выдернут из статьи и прекрасно работающий под Windows (но с тем условием, что использую _vscprintf для вычисления length).
нужно больше инфы. с каким стеком падает? что именно выводит? всю строку дословно, без замен на решетки #
пример именно этот гоняете или по факту другой?
Здравствуйте, 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.
Здравствуйте, watchmaker, Вы писали:
W>Чтобы исправить поведение стандарт предписывает в твоём случае использовать va_copy.
Как говорится, muchas gracias !
Уже разобрался, но пока до va_copy самостоятельно не докопался. Спасибо ещё раз за подсказку!
Здравствуйте, uzhas, Вы писали:
U>Здравствуйте, Tujh, Вы писали:
T>>Имеется следующий код, вроде стандартный, выдернут из статьи и прекрасно работающий под Windows (но с тем условием, что использую _vscprintf для вычисления length).
U>вот тут работает
Неопределённое поведение тем и опасно, что иногда проявляется как «тут работает».
Например, поведение может отличаться при использовании разных целевых платформ — на i386 делает вид что работает, а на x86-64 выводит чушь.
Здравствуйте, uzhas, Вы писали:
U>нужно больше инфы. с каким стеком падает? что именно выводит? всю строку дословно, без замен на решетки # U>пример именно этот гоняете или по факту другой?
Под решётками подразумевались произвольные символы в консоли обусловленные случайным содержимым памяти куда указывала структура args. Пример гонял именно этот. Специально написал его, для того, что бы вычленить ошибку из проекта.
Уже разобрался. в чем дело, после вызова vs(n)printf в Linux состояние args не восстанавливается и является неопределённым, в Windows состояние не меняется, вот и работает, случайно.
Вывод: хорошо, когда код проверяется сразу на нескольких платформах, находишь кучу мест неопределённого поведения.