Странное поведение (SEGFAULT) программы на C с использованием stdargs (va_start) - PullRequest
3 голосов
/ 03 августа 2010

Я написал функцию с переменным числом C, задача которой - выделить необходимую память для буфера, а затем вывести аргументы этой функции в этом буфере. Но я вижу странное поведение с этим. Это работает только один раз. Если у меня есть два вызова для этой функции, это segfaults.

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

char *xsprintf(char * fmt, ...)
{
    va_list ap;
    char *part;
    char *buf;
    size_t len = strlen(fmt)+1;

    va_start(ap, fmt);
    while (part = va_arg(ap, char *))
        len += strlen(part);
    va_end(ap);

    buf = (char*) malloc(sizeof(char)*len);

    va_start(ap, fmt);
    vsprintf(buf, fmt, ap);
    va_end(ap);

    return buf;
}

int main(int argc, const char *argv[])
{
    char *b;
    b = xsprintf("my favorite fruits are: %s, %s, and %s", "coffee", "C", "oranges");
    printf("size de buf is %d\n", strlen(b)); //this works. After it, it segfaults.
    /*
    free(b);
    b = NULL;
    */
    b = xsprintf("my favorite fruits are: %s, %s, and %s", "coffee", "C", "oranges");
    printf("size de buf is %d\n", strlen(b));
    printf("%s", b);
    return 0;
}

вот вывод этой программы:

size de buf is 46
[1]    4305 segmentation fault  ./xsprintftest

Я что-то не так делаю? Разве я не должен использовать va_start несколько раз в одной функции? Есть ли у вас альтернативы? Большое спасибо! :)

Ответы [ 5 ]

5 голосов
/ 03 августа 2010

Вы должны использовать vsnprintf.Используйте это дважды.Один раз с NULL целевым / нулевым размером, чтобы узнать длину буфера, который нужно выделить, затем второй раз, чтобы заполнить буфер.Таким образом, ваша функция будет работать, даже если все аргументы не являются строками.

Как написано, она не будет работать, если есть какие-либо нестроковые аргументы (%d, %x, %f и т. Д.).И подсчет количества % символов не является правильным способом получения количества аргументов.Ваш результат может быть слишком большим (если есть буквальные % символы, закодированные как %%) или слишком маленьким (если аргументы также необходимы для %*s, %.*d и т. Д. Спецификаторов ширины / точности).

3 голосов
/ 03 августа 2010

Передайте NULL как последний аргумент xsprintf ():

b = xsprintf("my favorite fruits are: %s, %s, and %s",
             "coffee", "C", "oranges", (void*)0);

Тогда ваш цикл while() увидит NULL и завершится правильно.

Как упоминает R ..в комментарии ниже и в другом ответе функция xsprintf завершится ошибкой, если есть другие аргументы формата.Вам лучше использовать vsprintf, как объяснено в другом ответе.

Я хотел просто продемонстрировать использование дозорного с va_arg.

2 голосов
/ 04 августа 2010

Проблема в том, что в фрагменте кода, где вы получаете доступ к списку va_arg() без определенного определенного конца:

va_start(ap, fmt);
while (part = va_arg(ap, char *))
    len += strlen(part);
va_end(ap);

Средства stdargs.h не имеют встроенного метода для определения того, когда наступает конец va_list() - вам нужно сделать это явно в соответствии с соглашением, которое вы придумали. Либо используя значение дозорного (как в ответе bstpierre ), либо указав счет. Счетчик может быть предоставленным явным параметром или неявным (например, путем подсчета количества спецификаторов формата в строке формата, как это делает семейство printf()).

Конечно, у вас также есть проблема, заключающаяся в том, что ваш код в настоящее время поддерживает только один тип спецификатора формата (%s), но я предположил, что на данный момент это намеренно.

2 голосов
/ 03 августа 2010

Прежде всего, попробуйте использовать vsnprintf. Это просто хорошая идея.

Это не твоя проблема, хотя. Ваша проблема в том, что вы не можете вызвать va_arg больше раз, чем есть аргументы. Он не возвращает количество аргументов. Вы должны либо передать параметр, указывающий, сколько их, либо извлечь количество специальных токенов в строке формата, чтобы выяснить, сколько их должно быть неявно.

Именно поэтому printf может задушить вашу программу, если вы передадите ей слишком мало аргументов; он просто будет вытаскивать вещи из стека.

1 голос
/ 05 августа 2010

Большое спасибо за ваши ответы и идеи!Поэтому я переписал свою функцию так:

void fatal(const char *msg)/*{{{*/
{
  fprintf(stderr, "program: %s", msg);
  abort ();
}/*}}}*/

void *xmalloc(size_t size)/*{{{*/
{
  register void *value = malloc(size);
  if (value == 0)
    fatal ("Virtual memory exhausted");
  return value;
}/*}}}*/

void *xrealloc(void *ptr, size_t size)/*{{{*/
{
  register void *value = realloc(ptr, size);
  if (value == 0)
    fatal ("Virtual memory exhausted");
  return value;
}/*}}}*/

char *xsprintf(const char *fmt, ...)/*{{{*/
{
    /* Heavily inspired from http://perfec.to/vsprintf/pasprintf */
    va_list args;
    char *buf;
    size_t bufsize;
    char *newbuf;
    size_t nextsize;
    int outsize;
    int FIRSTSIZE = 20;

    bufsize = 0;

    for (;;) {
        if(bufsize == 0){
            buf = (char*)  xmalloc(FIRSTSIZE);
            bufsize = FIRSTSIZE;
        }
        else{
            newbuf = (char *)xrealloc(buf, nextsize);
            buf = newbuf;
            bufsize = nextsize;
        }

        va_start(args, fmt);
        outsize = vsnprintf(buf, bufsize, fmt, args);
        va_end(args);

        if (outsize == -1) {
            /* Clear indication that output was truncated, but no
             * clear indication of how big buffer needs to be, so
             * simply double existing buffer size for next time.
             */
            nextsize = bufsize * 2;

        } else if (outsize == bufsize) {
            /* Output was truncated (since at least the \0 could
             * not fit), but no indication of how big the buffer
             * needs to be, so just double existing buffer size
             * for next time.
             */
            nextsize = bufsize * 2;

        } else if (outsize > bufsize) {
            /* Output was truncated, but we were told exactly how
             * big the buffer needs to be next time. Add two chars
             * to the returned size. One for the \0, and one to
             * prevent ambiguity in the next case below.
             */
            nextsize = outsize + 2;

        } else if (outsize == bufsize - 1) {
            /* This is ambiguous. May mean that the output string
             * exactly fits, but on some systems the output string
             * may have been trucated. We can't tell.
             * Just double the buffer size for next time.
             */
            nextsize = bufsize * 2;

        } else {
            /* Output was not truncated */
            break;
        }
    }
    return buf;
}/*}}}*/

И она работает как шарм!Спасибо миллион раз:)

...