Как я могу преодолеть противоречивое поведение snprintf в разных UNIX-подобных операционных системах? - PullRequest
3 голосов
/ 19 сентября 2008

Для man-страниц 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. Надеюсь, это объяснение в порядке.

Ответы [ 6 ]

4 голосов
/ 19 сентября 2008

вы можете создать оболочку 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;
      }
   }
2 голосов
/ 19 сентября 2008

Рассматривали ли вы портативную реализацию printf? Я искал один недавно и остановился на трио.

http://daniel.haxx.se/projects/trio/

1 голос
/ 19 сентября 2008

Существует целый ряд проблем с переносимостью * printf, и реально вы, вероятно, захотите выбрать один из трех путей:

  1. Требуется * printf, совместимый с c99, потому что 9 лет должно быть достаточно для любого, и просто сказать, что платформа сломана в противном случае.

  2. Иметь my_snprintf () с кучей # ifdef для конкретных платформ, для которых вы хотите поддерживать все вызовы vsnprintf () снизу (понимание наименьшего общего знаменателя - то, что у вас есть).

  3. Просто носите с собой копию vsnprintf () со своим кодом, для простых случаев использования это на самом деле довольно просто , а для других вы, вероятно, захотите посмотреть vstr и вы получите клиентские форматеры бесплатно.

... как и предполагали другие, вы можете сделать хак слияния # 1 и # 2, только для случая -1, но это рискованно из-за того, что c99 * printf может / действительно возвращает -1 в некоторых случаях условия.

Лично я бы порекомендовал просто использовать библиотеку строк, такую ​​как ustr , которая делает простые обходные пути для вас и дает вам управляемые строки бесплатно. Если вы действительно заботитесь, вы можете комбинировать с vstr .

1 голос
/ 19 сентября 2008

Ваш вопрос до сих пор неясен. Справочная страница , связанная с , говорит так:

Функции 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
}
0 голосов
/ 19 сентября 2008

Используйте взамен намного превосходящую asprintf ().

Это расширение GNU, но его стоит скопировать на целевую платформу, если оно недоступно изначально.

0 голосов
/ 19 сентября 2008

Я нашел один портативный способ предсказать и / или ограничить количество символов, возвращаемых 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;   
}
...