alknows
alknows

Reputation: 2044

How to calculate the length of output that sprintf will generate?

Goal: serialize data to JSON.

Issue: i cant know beforehand how many chars long the integer is.

i thought a good way to do this is by using sprintf()

size_t length = sprintf(no_buff, "{data:%d}",12312);
char *buff = malloc(length);
snprintf(buff, length, "{data:%d}",12312);
//buff is passed on ...

Of course i can use a stack variable like char a[256] instead of no_buff.

Question: But is there in C a utility for disposable writes like the unix /dev/null? Smth like this:

#define FORGET_ABOUT_THIS ...
size_t length = sprintf(FORGET_ABOUT_THIS, "{data:%d}",12312);

p.s. i know that i can also get the length of the integer through log but this ways seems nicer.

Upvotes: 31

Views: 29183

Answers (8)

Mohan Noone
Mohan Noone

Reputation: 711

Can get a reusable function that gives the length for any formatted string, with vsnprintf :

#include <stdio.h>
#include <stdarg.h>
//...
int fl(const char* format, ...) {
   int n;
   va_list args;
   va_start(args, format);
   n = vsnprintf(NULL,0,format,args);
   va_end(args);
   return n + 1;
}

Example usage (without malloc, instead using VLA as the buffer):

const char* format = "%s times %s\n";
int len = fl(format,"one","two");
if (len>0) {
    char buffer[len];
    sprint(buffer,format,"one","two");
    printf("Size is %d\n", len);
    printf("%s",buffer);
}

Upvotes: 0

DAG
DAG

Reputation: 445

Calling snprintf(nullptr, 0, ...) does return the size but it has performance penalty, because it will call IO_str_overflow and which is slow.

If you do care about performance, you can pre-allocate a dummy buffer and pass its pointer and size to ::snprintf. it will be several times faster than the nullptr version.

template<typename ...Args>
size_t get_len(const char* format, Args ...args) {
  static char dummy[4096]; // you can change the default size
  return ::snprintf(dummy, 4096, format, args...) + 1; // +1 for \0
}

Upvotes: 2

nintyfan
nintyfan

Reputation: 444

Printf supports %n format parameter, which means "write position of %n in output string to int value pointed by x-parameter), so:

int x;snprintf(NULL, 0, "Message: %s%n", "Error!",&x);

Should works!

Upvotes: 0

If you check the performance, you will running snprintf without an output buffer will take roughly the same time as a full invocation.

So I recommend you to use a smaller buffer just in case, and only call it a second time if the returned size exceeded the buffer size.

This uses C++'s std::string but I guess you can adapt it for your needs.

std::string format(const char* format, ...) {
    va_list args;
    va_start(args, format);
    char smallBuffer[1024];
    int size = vsnprintf(smallBuffer, sizeof smallBuffer, format, args);
    va_end(args);

    if (size < sizeof smallBuffer) 
        return std::string(smallBuffer);

    char buffer[size  + 1]; /* maybe malloc if it's too big */

    va_start(args, format);
    vsnprintf(buffer, sizeof buffer, format, args);
    va_end(args);
    return std::string(buffer);
}

This code will run 2x faster for strings under 1k compared to the longer ones.

Upvotes: 2

Brian Campbell
Brian Campbell

Reputation: 332866

You can call int len = snprintf(NULL, 0, "{data:%d}", 12312) to test how much space you need.

snprintf will print at most size characters, where size is the second argument, and return how many characters would have been necessary to print the whole thing, not counting the terminating '\0'. Because you pass in 0, it won't actually write anything out (and thus will avoid any null pointer exception that would happen by trying to dereference NULL), but it will still return the length that is needed to fit the whole output, which you can use to allocate your buffer.

At that point you can allocate and print to your buffer, remembering to include one more for the trailing '\0':

char *buf = malloc(len + 1);
snprintf(buf, len + 1, "{data:%d}", 12312);

Upvotes: 22

myaut
myaut

Reputation: 11504

Since C is where simple language, there is no such thing as "disposable buffers" -- all memory management are on programmers shoulders (there is GNU C compiler extensions for these but they are not standard).

cant know beforehand how many chars long the integer is.

There is much easier solution for your problem. snprintf knows!

On C99-compatible platforms call snprintf with NULL as first argument:

ssize_t bufsz = snprintf(NULL, 0, "{data:%d}",12312);
char* buf = malloc(bufsz + 1);
snprintf(buf, bufsz + 1, "{data:%d}",12312);

...

free(buf);

In older Visual Studio versions (which have non-C99 compatible CRT), use _scprintf instead of snprintf(NULL, ...) call.

Upvotes: 48

Dolda2000
Dolda2000

Reputation: 25855

This isn't strictly an answer to your question, but you may find it helpful nonetheless. It is not portable, but if you're running this on glibc, you can simply use asprintf() instead, which will do the memory allocation for you.

Upvotes: -1

M.M
M.M

Reputation: 141618

To just obtain the length you can write:

int length = snprintf(NULL, 0, "{data:%d}", 12312);

Note that the return type is int. It may return -1 in case of some sort of error. Make sure your input data doesn't include long strings that might cause the total length to exceed INT_MAX !

Upvotes: 6

Related Questions