2016-06-13 4 views
4

Я портирую очень старый (> 10y) код C современным Linux. Я получаю ошибку сегментации в специально написанном vsnprintf() обертки (по-видимому, его задача состоит в том, чтобы обнаружить дубликаты выходных строк и стажер их):Как вы называете vsnprintf() безопасно?

char* strVPrintf(const String fmt, va_list ap) 
{ 
    /* Guess we need no more than 50 bytes. */ 
    int n, size = 50; 
    char* p = (char*)memMalloc(size), q; 

    while (1) { 
    /* Try to print in the allocated space. */ 
    n = vsnprintf(p, size, fmt, ap); 
    /* If that worked, return the string. */ 
    if (n > -1 && n < size) { 
     break; 
    } 
    /* 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 */ 
    p = memRealloc(p, size); 
    } 

    q = strRegister(p); 
    memFree(p); 
    return q; 
} 

Автор, кажется, предположил, что стандартная vsnprintf() функции возвращает количество написанных символов и просто возвращает значение дозорного, если оно не получает достаточно места для форматирования всех аргументов. Это означает, что вы можете просто угадать размер буфера и при необходимости увеличить его.

Но в моей системе (Ubuntu 14.04, glibc 2.19) vnprintf вызывает ошибку сегментации при вызове с слишком большим количеством аргументов для предоставленного пространства. Изменилась ли семантика семьи snprintf(), что значительно изменилось? И каков современный способ обеспечить вам достаточное пространство для буфера?

+0

'человек vsnprintf': * Функции snprintf() и vsnprintf() не пишите больше байтов размера (включая завершающий нулевой байт ('\ 0')). Если результат был усечен из-за этого предела, возвращаемым значением является количество символов (исключая завершающий нулевой байт), которые были бы записаны в финальную строку, если бы было достаточно свободного места. Таким образом, возвращаемое значение размера или больше означает, что результат был усечен. * Эта часть программы кажется правильной. – EOF

+2

Вы не проверяете наличие сбоев в распределении памяти. – user694733

+0

Что такое 'String'? Я подозреваю, что это 'char *', но вы также должны предоставить эту информацию. –

ответ

7

Это правильный способ использовать snprintf и vsnprintf на любой операционной системе, кроме SunOS 4 (который имеет устарел на 20 лет), поэтому ваша проблема находится где-то в другом месте.

Я сделаю чистое предположение и скажу, что я почти уверен, что ваша проблема в том, что вы передаете va_list ap в vsnprintf, который его потребляет, а затем вы ожидаете его сброса при следующем вызове. Это неверно и перестало работать в gcc много лет назад (потому что оно работало только на некоторых архитектурах).

Изменение:

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

To:

va_list apc; 
va_copy(apc, ap); 
n = vsnprintf(p, size, fmt, apc); 
va_end(apc); 

И посмотреть, если это помогает.

Вот простой тест, чтобы увидеть, что происходит:

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

void 
foo(const char *fmt, va_list ap) 
{ 
#ifdef BAD 
    vprintf(fmt, ap); 
#else 
    va_list apc; 
    va_copy(apc, ap); 
    vprintf(fmt, apc); 
    va_end(apc); 
#endif 
    vprintf(fmt, ap); 
} 

void 
bar(const char *fmt, ...) 
{ 
    va_list ap; 
    va_start(ap, fmt); 
    foo(fmt, ap); 
    va_end(ap); 
} 

int 
main(int argc, char **argv) 
{ 
    bar("foo %s\n", "bar"); 
    return 0; 
} 

При запуске я получаю это:

$ cc -o foo foo.c && ./foo 
foo bar 
foo bar 
$ cc -DBAD -o foo foo.c && ./foo 
foo bar 
foo ���� 
+1

Вы правы, это была проблема. Как обычно, меня отвлекало то, что короткие строки были напечатаны без проблем, но длинные строки разбились, поэтому я решил проблему с переполнением буфера. Я должен был знать, что неопределенное поведение никогда не заслуживает доверия, и * будет * маскироваться как другая проблема, если вы дадите ему шанс! –

2

Как я понимаю, его цель - определить размер, необходимый sprintf, чтобы полностью записать строку вывода в буфер. Для этого есть функция, которая делает это для вас: asprintf (или vasprintf здесь).

Прототип

int vasprintf(char **strp, const char *fmt, va_list ap); 

Просто используйте его следующим образом:

String strVPrintf(const String fmt, va_list ap) 
{ 
    char *ans; 
    int n; 
    n = vasprintf(&ans, fmt, ap); 
    // do the checks 
    return ans; 
} 

С помощью этой функции вы не нужна эта обертка больше, я думаю.

+0

'asprintf()' и 'vasprintf()' являются нестандартными функциями. –

+1

@AndrewHenle OP использует glibc. – Boiethios

+0

ОП не выразил желания написать непереносимый код. –

2

Неуверенного на ваших, но моей странице на переменном аргументе список говорит:

СОВМЕСТИМОСТИ
Эти макросы не совместимы с историческими макросами, которые они заменяют. A обратную совместимую версию можно найти в файле include <varargs.h>.

Как вы сказали, это очень старый код, возможно, va_list, полученный в этой подпрограмме, не является va_list, ожидаемым vsnprintf. Вы должны сначала попытаться извлечь все параметры с одного заголовка, то другой, чтобы быть уверенным (обычно vsnprintf является stdarg.h совместимый)

Смежные вопросы