Как я могу преодолеть несогласованное поведение snprintf в разных UNIX-подобных операционных системах?
-
01-07-2019 - |
Вопрос
Для каждой страницы руководства snprintf возвращает количество записанных байтов, начиная с glibc версии 2.2.Но в более ранних версиях libc2.2 и HP-UX он возвращает положительное целое число, что может привести к переполнению буфера.
Как можно преодолеть это и написать переносимый код?
Редактировать :За неимением большей ясности
Этот код отлично работает в lib 2.3.
if ( snprintf( cmd, cmdLen + 1, ". %s%s", myVar1, myVar2 ) != cmdLen )
{
fprintf( stderr, "\nError: Unable to copy bmake command!!!");
returnCode = ERR_COPY_FILENAME_FAILED;
}
Он возвращает длину строки (10) в Linux.Но тот же код возвращает положительное число, большее, чем количество символов, напечатанных на машине HP-UX.Надеюсь, это объяснение подойдет.
Решение
вы можете создать оболочку snprintf, которая возвращает -1 для каждого случая, когда в буфере недостаточно места.
См. справочная страница дополнительные документы.Есть также пример, который ставит под угрозу все случаи.
while (1) {
/* Try to print in the allocated space. */
va_start(ap, fmt);
n = vsnprintf (p, size, fmt, ap);
va_end(ap);
/* If that worked, return the string. */
if (n > -1 && n < size)
return p;
/* Else try again with more space. */
if (n > -1) /* glibc 2.1 */
size = n+1; /* precisely what is needed */
else /* glibc 2.0 */
size *= 2; /* twice the old size */
if ((np = realloc (p, size)) == NULL) {
free(p);
return NULL;
} else {
p = np;
}
}
Другие советы
Рассматривали ли вы портативную реализацию printf?Некоторое время назад я искал один и остановился на трио.
Ваш вопрос пока неясен.А справочная страница связанный с говорит так:
Функции snprintf() и vsnprintf(). не записывайте больше, чем размер байтов (в том числе след ' 0').Если выходные данные были усечены из-за этого ограничения, возвращаемое значение представляет собой количество символов (не включая завершающий '\0'), которые бы были записаны в последнюю строку, если было доступно достаточно места.Таким образом, возвращаемое значение размера или больше означает, что выходные данные были усечены.
Итак, если вы хотите знать, был ли усечен ваш вывод:
int ret = snprintf(cmd, cmdLen + 1, ". %s%s", myVar1, myVar2 ) == -1)
if(ret == -1 || ret > cmdLen)
{
//output was truncated
}
else
{
//everything is groovy
}
Существует целый ряд проблем с переносимостью *printf, и на самом деле вы, вероятно, захотите пойти по одному из трех путей:
Требуется *printf, совместимый с c99, потому что 9 лет должно хватить любому, а в противном случае просто скажите, что платформа сломана.
Имейте my_snprintf() с кучей #ifdef для конкретных платформ, которые вы хотите поддерживать, все вызывающие vsnprintf() внизу (понимание того, что у вас есть наименьший общий знаменатель).
Просто носите с собой копию vsnprintf() вместе с вашим кодом, для простых случаев использования это действительно так. довольно просто и для других, на которые вы, вероятно, захотите посмотреть встр и вы получите клиентские форматировщики бесплатно.
... как предлагали другие люди, вы можете сделать хак, объединив # 1 и # 2, только для случая -1, но это рискованно из-за того, что c99 *printf может / действительно возвращает -1 в определенных условиях.
Лично я бы рекомендовал просто использовать библиотека строк, такая как ustr, который выполняет простые обходные пути и бесплатно предоставляет управляемые строки.Если вам действительно интересно, вы можете объединить с встр.
Я нашел один портативный способ предсказать и/или ограничить количество символов, возвращаемых sprintf и связанными с ним функциями, но он неэффективен, и многие считают его неэлегантным.
Что вы делаете, так это создаете временный файл с помощью tmpfile(), fprintf() (который надежно возвращает количество записанных байтов), затем перематываете назад и читаете весь или часть текста в буфер.
Пример:
int my_snprintf(char *buf, size_t n, const char *fmt, ...)
{
va_list va;
int nchars;
FILE *tf = tmpfile();
va_start(va, n);
nchars = vfprintf(tf, fmt, va);
if (nchars >= (int) n)
nchars = (int) n - 1;
va_end(va);
memset(buf, 0, 1 + (size_t) nchars);
if (nchars > 0)
{
rewind(tf);
fread(buf, 1, (size_t) nchars, tf);
}
fclose(tf);
return nchars;
}
Вместо этого используйте гораздо более совершенный метод asprintf().
Это расширение GNU, но его стоит скопировать на целевую платформу, если оно недоступно изначально.