52

I'm searching for a sprintf()-like implementation of a function that automatically allocates required memory. So I want to say

char *my_str = dynamic_sprintf("Hello %s, this is a %.*s nice %05d string", a, b, c, d);

and my_str receives the address of an allocated block of memory that holds the result of this sprintf().

In another forum, I read that this can be solved like this:

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

int main()
{
    char    *ret;
    char    *a = "Hello";
    char    *b = "World";
    int     c = 123;

    int     numbytes;

    numbytes = sprintf((char *)NULL, "%s %d %s!", a, c, b);
    printf("numbytes = %d", numbytes);

    ret = (char *)malloc((numbytes + 1) * sizeof(char));
    sprintf(ret, "%s %d %s!", a, c, b);

    printf("ret = >%s<\n", ret);
    free(ret);

    return 0;
}

But this immediately results in a segfault when the sprintf() with the null pointer is invoked.

So any idea, solution or tips? A small implementation of a sprintf()-like parser that is placed in the public domain would already be enough, then I could get it myself done.

Thanks a lot!

chqrlie
  • 131,814
  • 10
  • 121
  • 189
the-shamen
  • 521
  • 1
  • 4
  • 3

8 Answers8

50

Here is the original answer from Stack Overflow. As others have mentioned, you need snprintf not sprintf. Make sure the second argument to snprintf is zero. That will prevent snprintf from writing to the NULL string that is the first argument.

The second argument is needed because it tells snprintf that enough space is not available to write to the output buffer. When enough space is not available snprintf returns the number of bytes it would have written, had enough space been available.

Reproducing the code from that link here ...

char* get_error_message(char const *msg) {
    size_t needed = snprintf(NULL, 0, "%s: %s (%d)", msg, strerror(errno), errno) + 1;
    char  *buffer = malloc(needed);
    sprintf(buffer, "%s: %s (%d)", msg, strerror(errno), errno);
    return buffer;
}
marczellm
  • 1,224
  • 2
  • 18
  • 42
Tarun
  • 639
  • 6
  • 3
  • 4
    Shouldn't you add 1 to `needed` to account for the terminating null character? – beldaz Jan 24 '16 at 04:03
  • 4
    Didn't spot the +1 at the end of the first line at first (it was outside the visible area): `size_t needed = snprintf(...) + 1;` – user2421739 Feb 10 '17 at 18:29
  • 2
    I was a little worried about whether passing NULL here invoked undefined behaviour, so I checked, and can confirm it's explicitly allowed by the C standard - see https://stackoverflow.com/a/57646312/1709587. – Mark Amery Aug 25 '19 at 13:28
  • 3
    Technically, this code is unsafe because `errno` could change between the call to `snprintf` and the call to `sprintf`, which is not protected against buffer overflow. You should use `snprintf` for both calls and you should save `errno` to a local variable before the first call. – chqrlie Oct 13 '19 at 17:28
37

GNU and BSD have asprintf and vasprintf that are designed to do just that for you. It will figure out how to allocate the memory for you and will return null on any memory allocation error.

asprintf does the right thing with respect to allocating strings -- it first measures the size, then it tries to allocate with malloc. Failing that, it returns null. Unless you have your own memory allocation system that precludes the use of malloc, asprintf is the best tool for the job.

The code would look like:

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()
{
    char*   ret;
    char*   a = "Hello";
    char*   b = "World";
    int     c = 123;

    int err = asprintf(&ret, "%s %d %s!", a, c, b );
    if (err == -1) {
        fprintf(stderr, "Error in asprintf\n");
        return 1;
    }

    printf("ret = >%s<\n", ret);
    free(ret);

    return 0;
}
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
Mike Axiak
  • 11,827
  • 2
  • 33
  • 49
  • 10
    asprintf() would be the function of my choice - but unfortunatelly, its non standard and not portable - bad! – the-shamen Sep 23 '10 at 06:53
  • 1
    @the-shamen - what you are asking for is by definition non standard and not portable. Get the source for `asprintf` and pull it into your project if you need to, or reimplement it independently. – bstpierre Sep 23 '10 at 18:22
  • 19
    I have not heard of an `asprintf()` that returns a pointer. The one that comes with GNU and BSD (and provided by gnulib and libstrl) has the same return value as the equivalent `printf()` call and takes a pointer to a pointer as the first argument. So, `char *s; int ret = asprintf(&s, "%s %d %s!", a, c, b);` with error on `ret == -1`. Just wondering, what systems/libraries provide an `asprintf()` which returns a pointer like in this answer? – binki Feb 07 '14 at 04:14
20

If you can live with GNU/BSD extentions, the question is already answered. You can use asprintf() (and vasprintf() for building wrapper functions) and be done.

But snprintf() and vsnprintf() are mandated by POSIX, according to the manpage, and the latter can be used to build up your own simple version of asprintf() and vasprintf().

int
vasprintf(char **strp, const char *fmt, va_list ap)
{
    va_list ap1;
    int len;
    char *buffer;
    int res;

    va_copy(ap1, ap);
    len = vsnprintf(NULL, 0, fmt, ap1);

    if (len < 0)
        return len;

    va_end(ap1);
    buffer = malloc(len + 1);

    if (!buffer)
        return -1;

    res = vsnprintf(buffer, len + 1, fmt, ap);

    if (res < 0)
        free(buffer);
    else
        *strp = buffer;

    return res;
}

int
asprintf(char **strp, const char *fmt, ...)
{
    int error;
    va_list ap;

    va_start(ap, fmt);
    error = vasprintf(strp, fmt, ap);
    va_end(ap);

    return error;
}

You can do some preprocessor magic and use your versions of functions only on systems that don't support them.

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
Pavel Šimerda
  • 5,783
  • 1
  • 31
  • 31
  • 1
    You can only pass `va_list` variable to one function. To use `vsnprintf()` twice as you do in `vasprintf()` you should use `va_copy()`. – Ilia K. Jun 30 '14 at 18:07
  • 1
    It will be if you add `va_end(ap1)` before return from `vasprintf()` (e.g. right after a call to `vsnprintf()`). – Ilia K. Jul 10 '14 at 12:09
  • 1
    you forgot to set the strp in the end of vasprintf – 5andr0 Jul 12 '15 at 13:14
  • There is some error handling in the code (it handles failure of `calloc`) but it still blows up if `vsnprintf` were to fail: the size calculation would wrap around if less than -1 is returned as error code for the first `vsnprintf`. If the second invocation of `vsnprintf` fails, the buffer is leaked. – user2421739 Feb 10 '17 at 18:34
  • 1
    @user2421739 I fixed that, since this answer is quite old. – Marco Bonelli Jul 15 '21 at 02:11
11
  1. If possible, use snprintf -- it gives an easy way to measure the size of data that would be produced so you can allocate space.
  2. If you really can't do that, another possibility is printing to a temporary file with fprintf to get the size, allocate the memory, and then use sprintf. snprintf is definitely the preferred method though.
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
6

The GLib library provides a g_strdup_printf function that does exactly what you want, if linking against GLib is an option. From the documentation:

Similar to the standard C sprintf() function but safer, since it calculates the maximum space required and allocates memory to hold the result. The returned string should be freed with g_free() when no longer needed.

Paul Kuliniewicz
  • 2,711
  • 18
  • 24
3

POSIX.1 (aka IEEE 1003.1-2008) provides open_memstream:

char *ptr;
size_t size;
FILE *f = open_memstream(&ptr, &size);
fprintf(f, "lots of stuff here\n");
fclose(f);
write(1, ptr, size); /* for example */
free(ptr);

open_memstream(3) is available on at least Linux and macOS and has been for some years. The converse of open_memstream(3) is fmemopen(3) which makes the contents of a buffer available for reading.

If you just want a single sprintf(3) then the widely-implemented but non-standard asprintf(3) might be what you want.

John Haxby
  • 59
  • 4
0
/*  casprintf print to allocated or reallocated string

char *aux = NULL;
casprintf(&aux,"first line\n");
casprintf(&aux,"seconde line\n");
printf(aux);
free(aux);
*/
int vcasprintf(char **strp,const char *fmt,va_list ap)
{
  int ret;
  char *strp1;
  char *result;
  if (*strp==NULL)
     return vasprintf(strp,fmt,ap);

  ret=vasprintf(&strp1,fmt,ap); // ret = strlen(strp1) or -1
  if (ret == -1 ) return ret;
  if (ret==0) {free(strp1);return strlen(*strp);}

  size_t len = strlen(*strp);
  *strp=realloc(*strp,len + ret +1);
  memcpy((*strp)+len,strp1,ret+1);
  free(strp1);
  return(len+ret);
}

int casprintf(char **strp, const char *fmt, ...)
{
 int ret;
 va_list ap;
 va_start(ap,fmt);
 ret =vcasprintf(strp,fmt,ap);
 va_end(ap);
 return(ret);
}
  • Welcome to Stack Overflow. While your code may provide the answer to the question, please add context around it so others will have some idea what it does and why it is there. – Theo Oct 11 '19 at 11:56
0

My version (v3) of https://stackoverflow.com/a/10388547/666907 – a bit more universal [note I'm a C++ noob, use at your own risk :P]:

#include <cstdarg>

char* myFormat(const char* const format...) {
  // `vsnprintf()` changes `va_list`'s state, so using it after that is UB.
  // We need the args twice, so it is safer to just get two copies.
  va_list args1;
  va_list args2;
  va_start(args1, format);
  va_start(args2, format);

  size_t needed = 1 + vsnprintf(nullptr, 0, format, args1);

  // they say to cast in C++, so I cast…
  // https://stackoverflow.com/a/5099675/666907
  char* buffer = (char*) malloc(needed);

  vsnprintf(buffer, needed, format, args2);

  va_end(args1);
  va_end(args2);

  return buffer;
}

char* formatted = myFormat("Foo %s: %d", "bar", 456);
Serial.println(formatted); // Foo bar: 456

free(formatted); // remember to free or u will have a memory leak!
MacDada
  • 2,000
  • 1
  • 14
  • 24
  • 1
    This does not work because you use `args` twice and do not use `va_end()`. You must use `va_copy` or `va_start()` and `va_end()` twice. Furthermore, using `vsprintf` is risky. Why not use `vsnprintf` twice and be safe? – chqrlie Jul 05 '22 at 21:09
  • "This does not work" Well, it does work, at least for my `Foo bar: 456` example and other examples I tested it on. "You must use va_copy or va_start() and va_end() twice." I'm noob, I just copy–pasted some code, modified is as a blind monkey, and it somehow worked xD "Furthermore, using vsprintf is risky. Why not use vsnprintf twice and be safe?" IDK. The example I modified used snprintf and sprintf. I changed them to "v" versions for easy args. It was suggested to use snprintf twice, but I'm noob. Can you modify it better? Please do, I will gladly use a better version :) – MacDada Jul 05 '22 at 21:17
  • @chqrlie Well, I've learnt a bit more of C++ to now understand what you meant :D I just updated the example with UB and memory leak fixed ;-) – MacDada Dec 21 '22 at 01:50