Strange behavior (SEGFAULT) of a C program using stdargs (va_start)

I wrote a variational function C, the mission of which is to allocate the necessary memory for the buffer, and then sprintf the arguments given to this function in this buffer. But I see strange behavior. It only works once. If I have two calls to this function, this can be disabled.

#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;
}

here is the output of this program:

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

Am I doing something wrong? Didn't I use it va_startseveral times in one function? Do you have any alternatives? Many thanks!:)

+3
source share
5 answers

vsnprintf. . NULL / , , , , . , , .

, , - (%d, %x, %f ..). % . ( % , %%) ( %*s, %.*d .. /).

+5

NULL xsprintf():

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

while() NULL .

, xsprintf , . vsprintf, .

, va_arg.

+3

, vsnprintf. .

. , va_arg , . . , , , , , .

printf , ; .

+2

, , va_arg() :

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

stdargs.h , va_list() - , , . ( bstpierre answer), . , , (, , , printf()).

Of course, you also have a problem that your code currently only supports one type of format specifier ( %s), but I assumed this is intentional at the moment.

+2
source

Thanks so much for your answers and ideas! So I rewrote my function as follows:

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;
}/*}}}*/

And it works like a charm! Thanks a million times :)

+1
source

Source: https://habr.com/ru/post/1757831/


All Articles