snprintf return value

I usually have a static buffer and I don't want to use a dynamic buffer.

char buf[BUFLEN];

I have to construct a long string, and it is quite easy to call snprintf multiple times:

int n = 0; n += snprintf(&buf[n], BUFLEN - n, ...); n += snprintf(&buf[n], BUFLEN - n, ...); n += snprintf(&buf[n], BUFLEN - n, ...); n += snprintf(&buf[n], BUFLEN - n, ...);

I was convinced the return value was the real number of characters written in buf, not counting final '\0'. In this case, the above code works.

I just noticed the return value of snprintf() is the number of characters of the generated string as if the buffer was long enough. So the above code doesn't work.

The return value of snprintf() is useful if you want to dynamically reallocate the buffer so it can store all the string. In my case I would prefer to truncate the string at the end of the static buffer. The above code should be redesigned as:

int n = 0; n += snprintf(&buf[n], BUFLEN - n, ...); if (n >= BUFLEN - n) return; n += snprintf(&buf[n], BUFLEN - n, ...); if (n >= BUFLEN - n) return; n += snprintf(&buf[n], BUFLEN - n, ...); if (n >= BUFLEN - n) return; n += snprintf(&buf[n], BUFLEN - n, ...); if (n >= BUFLEN - n) return;

The code starts being not readable. I'm thinking to create a new snprintf2():

int snprintf2(char *str, size_t size, const char *format, ...) { va_list args;

va_start(args, format); int n = vsnprintf(str, size, format, args); va_end(args); return (n >= size) ? (size - 1) : n; }

Reply to
pozz
Loading thread data ...

Keep in mind that snprintf can even return negative values as error codes.

--
Reinhardt
Reply to
Reinhardt Behm

Could you provide a test case for a negative return value from snprintf()?

Reply to
pozz

This is going to add a good extra layer of inefficiency, stop optimisations that your compiler could do (such as converting some cases to strcat), and stop the format checking provided by gcc unless you manually add the right attributes.

I'd be tempted to put things in a macro here:

#define snprintf2(str, size, format...) \ ({ int n = snprintf(str, size, ## format); \ (n >= size) ? (size - 1) : n })

Yes, it's a gcc extension - but the point is to get the gcc features of warnings and optimisation. If you are not using gcc, then I guess your function is okay.

Reply to
David Brown

I'd be seriously asking yourself why you prefer that. Arbitrary unexpected truncation of data should not be tolerated.

Clifford Heath.

Reply to
Clifford Heath

Am 10.12.2018 um 11:20 schrieb pozz:

What exactly do you expect to gain by doing it that way?

Convinced by what? I mean, come on, what do we (as a community) pay documentation writers for, if nobody can be bothered to read what they created?

And that's not at all a coincidence. It's precisely the reason why the return value of v?snprintf() is defined slightly differently from that of all the other *printf() functions.

Or much simpler as

snprintf(buf, BUFLEN, ... ... ... ... ); buf[BUFLEN - 1] = '\0'; // just in case we hit bottom

That said, if you absolutely have to, there's always the %n output format...

Reply to
Hans-Bernhard Bröker

One would assume there's some logic between appending bits of string.

Now who needs to read the documentation? snprintf guarantees always to null-terminate.

Clifford Heath.

Reply to
Clifford Heath

Well, overrunning a buffer is a bug, so it's just a choice of which type of misbehaviour you prefer. If I'm writing to some logfile, for instance, truncating the message is a lot better than overwriting memory!

Cheers

Phil Hobbs

--
Dr Philip C D Hobbs 
Principal Consultant 
ElectroOptical Innovations LLC 
Optics, Electro-optics, Photonics, Analog Electronics 

160 North State Road #203 
Briarcliff Manor NY 10510 

hobbs at electrooptical dot net 
http://electrooptical.net
Reply to
Phil Hobbs

Right, overflow protection is important. So is data integrity.

So make sure you have a big enough buffer for your data, possibly by truncating the data before you snprintf(), or by using %.23s or the like. Either way you should handle truncation *deliberately*, not by simply clamping to a buffer length.

Clifford Heath.

Reply to
Clifford Heath

Not all data is equally valuable. And not all data is of a size that you know you can handle fully. I have no idea what the OP's original needs are here, but a prime example is, as Phil said, a log file. Often the details of what are in the log file are not critical - but avoiding buffer overruns or limiting the bandwidth used /is/ critical. So techniques like truncation or rate limiting are entirely reasonable here.

Reply to
David Brown

And as long as that's deliberate, that's fine. But the OP's code was appending some number of strings to some buffer. It looked far less deliberate than it ought, though the effect may be as intended. It also splits the buffer-bounds check into many pieces, so any slip fails the whole thing.

We used to have a printf that took a putchar function as argument. You could pass one that filled a buffer with truncation. Even easier now that C++ supports lambda closures.

Clifford Heath.

Reply to
Clifford Heath

SO it seems you can have both in this case - if said buffer is truncated you can detect that and reallocate. That being said, prevention is better, IMO - don't set it up to where it can overrun nor be truncated. If you have a variation on %s in there, length-check the string. All other cases should enable the use of like %32.23lf to limit things.

The one nasty thing about using a static bnuffer is that you'd need to be able to test it thoroughly and any "reallocation" would require a recompile.

--
Les Cargill
Reply to
Les Cargill

Am 10.12.18 um 11:32 schrieb pozz:

The standard allows negative return values only for encoding errors. So for typical systems (i.e. sizeof(char) < sizeof(int), wchar_t is Unicode code points), a %c with an argument outside the range of char should work. Or a %lc with an argument that is not a Unicode code point. Or a %ls with an argument that is an array containing values that are not valid Unicode code points.

Philipp

Reply to
Philipp Klaus Krause

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.