snprintf return value

Started by pozz December 10, 2018
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;
   }
AT Monday 10 December 2018 18:20, pozz wrote:

> 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; > }
Keep in mind that snprintf can even return negative values as error codes. -- Reinhardt
Il 10/12/2018 11:24, Reinhardt Behm ha scritto:
> AT Monday 10 December 2018 18:20, pozz wrote: > >> 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; >> } > > Keep in mind that snprintf can even return negative values as error codes.
Could you provide a test case for a negative return value from snprintf()?
On 10/12/18 11:20, pozz wrote:
> 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; > }
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.
On 10/12/18 9:20 pm, pozz wrote:
> 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.
I'd be seriously asking yourself why you prefer that. Arbitrary unexpected truncation of data should not be tolerated. Clifford Heath.
Am 10.12.2018 um 11:20 schrieb pozz:
> 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, ...);
What exactly do you expect to gain by doing it that way?
> I was convinced the return value was the real number of characters > written in buf,
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?
> The return value of snprintf() is useful if you want to dynamically > reallocate the buffer so it can store all the string.
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.
> 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;
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...
On 11/12/18 10:47 am, Hans-Bernhard Br�ker wrote:
> Or much simpler as > > snprintf(buf, BUFLEN, > ... > ... > ... > ... > );
One would assume there's some logic between appending bits of string.
> buf[BUFLEN - 1] = '\0'; // just in case we hit bottom
Now who needs to read the documentation? snprintf guarantees always to null-terminate. Clifford Heath.
On 12/10/18 4:49 PM, Clifford Heath wrote:
> On 10/12/18 9:20 pm, pozz wrote: >> 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. > > I'd be seriously asking yourself why you prefer that. > Arbitrary unexpected truncation of data should not be tolerated. > > 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
On 12/12/18 3:41 am, Phil Hobbs wrote:
> On 12/10/18 4:49 PM, Clifford Heath wrote: >> On 10/12/18 9:20 pm, pozz wrote: >>> 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. >> >> I'd be seriously asking yourself why you prefer that. >> Arbitrary unexpected truncation of data should not be tolerated. >> >> 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!
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.
On 12/12/18 00:34, Clifford Heath wrote:
> On 12/12/18 3:41 am, Phil Hobbs wrote: >> On 12/10/18 4:49 PM, Clifford Heath wrote: >>> On 10/12/18 9:20 pm, pozz wrote: >>>> 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. >>> >>> I'd be seriously asking yourself why you prefer that. >>> Arbitrary unexpected truncation of data should not be tolerated. >>> >>> 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! > > 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. >
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.