Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

sprintf and the trailing null

3,757 views
Skip to first unread message

Rick C. Hodgin

unread,
Jul 6, 2015, 9:17:17 AM7/6/15
to
Two questions:

(1) Is there a version of sprintf() without a trailing NULL?
(2) Why does sprintf() automatically insert a trailing NULL?

TYIA!

Best regards,
Rick C. Hodgin

John McCue

unread,
Jul 6, 2015, 9:31:49 AM7/6/15
to
Rick C. Hodgin <rick.c...@gmail.com> wrote:
> Two questions:
>
> (1) Is there a version of sprintf() without a trailing NULL?

I have not seen any, but what are you trying to
accomplish ?

> (2) Why does sprintf() automatically insert a trailing NULL?

You can see why if you replace the 'trailing NULL'
with another character and print out the string.

>
> TYIA!
>
> Best regards,
> Rick C. Hodgin

John

Rick C. Hodgin

unread,
Jul 6, 2015, 9:38:05 AM7/6/15
to
On Monday, July 6, 2015 at 9:31:49 AM UTC-4, John McCue wrote:
> Rick C. Hodgin <rick.c...@gmail.com> wrote:
> > Two questions:
> > (1) Is there a version of sprintf() without a trailing NULL?
> I have not seen any, but what are you trying to accomplish ?

One example:

char buffer[8];
sprintf(buffer, "%04u%02%02u", year, month, day);

> > (2) Why does sprintf() automatically insert a trailing NULL?
>
> You can see why if you replace the 'trailing NULL' with another
> character and print out the string.

Why not options:

sprintf0(buffer, "%04u", value); // NULL-terminated form
sprintf(buffer, "%04u\0", value); // Manually terminated (if needed)

Or at this point in support of legacy systems, a new function:
rawprintf(buffer, "%04u\0", value); // No NULL-termination by default

Reinhardt Behm

unread,
Jul 6, 2015, 9:58:43 AM7/6/15
to
How does this function distinguish between these two format strings?

char *fmt1[] = { '%', '0', '4', 'u', '\0' }
which is the same as writing "%04u"
and
char *fmt2[] = { '%', '0', '4', 'u', '\0', '\0' }
which is the same as writing "%04u\0"

For the rare case you can always write your above example as
char buffer[8];
char buffer1[sizeof(buffer) + 1];
sprintf(buffer1, "%04u%02%02u", year, month, day);
strncpy(buffer, buffer1, sizeof(buffer));


--
Reinhardt

Rick C. Hodgin

unread,
Jul 6, 2015, 10:12:29 AM7/6/15
to
On Monday, July 6, 2015 at 9:58:43 AM UTC-4, Reinhardt Behm wrote:
> Rick C. Hodgin wrote:
>
> > On Monday, July 6, 2015 at 9:31:49 AM UTC-4, John McCue wrote:
> >> Rick C. Hodgin <rick.c...@gmail.com> wrote:
> >> > Two questions:
> >> > (1) Is there a version of sprintf() without a trailing NULL?
> >> I have not seen any, but what are you trying to accomplish ?
> >
> > One example:
> >
> > char buffer[8];
> > sprintf(buffer, "%04u%02%02u", year, month, day);
> >
> >> > (2) Why does sprintf() automatically insert a trailing NULL?
> >>
> >> You can see why if you replace the 'trailing NULL' with another
> >> character and print out the string.
> >
> > Why not options:
> >
> > sprintf0(buffer, "%04u", value); // NULL-terminated form
> > sprintf(buffer, "%04u\0", value); // Manually terminated (if needed)
> >
> > Or at this point in support of legacy systems, a new function:
> > rawprintf(buffer, "%04u\0", value); // No NULL-termination by default
>
> How does this function distinguish between these two format strings?
>
> char *fmt1[] = { '%', '0', '4', 'u', '\0' }
> which is the same as writing "%04u"
> and
> char *fmt2[] = { '%', '0', '4', 'u', '\0', '\0' }
> which is the same as writing "%04u\0"

It's not the same. The '\0' is interpreted by the compiler as a
NULL. In order to have the sam, you'd need:

char *fmt1[] = { '%', '0', '4', 'u', '\', '0', '\0' }

...physically embedding the \ and 0 as separate characters, then
the trailing null.

> For the rare case you can always write your above example as
> char buffer[8];
> char buffer1[sizeof(buffer) + 1];
> sprintf(buffer1, "%04u%02%02u", year, month, day);
> strncpy(buffer, buffer1, sizeof(buffer));

Yes. I'm thinking this double-processing could be avoided with
rawprintf(). Isn't C all about speed? I mean even the type for
int is supposed to be fast. Why not a common operation like
preparing an output string for disk, display, network traffic,
or future processing?

Jens Thoms Toerring

unread,
Jul 6, 2015, 10:16:02 AM7/6/15
to
Rick C. Hodgin <rick.c...@gmail.com> wrote:
> On Monday, July 6, 2015 at 9:31:49 AM UTC-4, John McCue wrote:
> > Rick C. Hodgin <rick.c...@gmail.com> wrote:
> > > Two questions:
> > > (1) Is there a version of sprintf() without a trailing NULL?
> > I have not seen any, but what are you trying to accomplish ?

> One example:

> char buffer[8];
> sprintf(buffer, "%04u%02%02u", year, month, day);

> > > (2) Why does sprintf() automatically insert a trailing NULL?
> >
> > You can see why if you replace the 'trailing NULL' with another
> > character and print out the string.

> Why not options:

> sprintf0(buffer, "%04u", value); // NULL-terminated form
> sprintf(buffer, "%04u\0", value); // Manually terminated (if needed)

As someone else already pinted out, you'd need a something
different for the "don't output a trailing '\0'" marker...

> Or at this point in support of legacy systems, a new function:
> rawprintf(buffer, "%04u\0", value); // No NULL-termination by default

because the 's' at the start of sprintf() stands for "string"
and strings are strings because the have a'\0' at the end -
otherwise it would be just a char array.

What would be an sprintf() variant be useful that doens't write
a trailing '\0'? The only reason I can think of is that you want
to write to some memory location of restricted size and the trai-
ling '\0' is one too much. But in that case you can use snprintf()
to ensure that no more that the number of chars you want get out-
put, i.e. in your example

snprint( buffer, 4, "%04u\n", value );

would probably do the trick (this, of course, assumes that 'value'
never is larger than 9999).
Regards, Jens
--
\ Jens Thoms Toerring ___ j...@toerring.de
\__________________________ http://toerring.de

Rick C. Hodgin

unread,
Jul 6, 2015, 10:20:11 AM7/6/15
to
On Monday, July 6, 2015 at 10:16:02 AM UTC-4, Jens Thoms Toerring wrote:
> Rick C. Hodgin <rick.c...@gmail.com> wrote:
> > Or at this point in support of legacy systems, a new function:
> > rawprintf(buffer, "%04u\0", value); // No NULL-termination by default
>
> because the 's' at the start of sprintf() stands for "string"
> and strings are strings because the have a'\0' at the end -
> otherwise it would be just a char array.

Agreed. So why isn't there a version like printf() which doesn't
put a trailing NULL the way sprintf() does, but one that does so
to a memory block? Maybe something like memprintf() or bufprintf()?

Rick C. Hodgin

unread,
Jul 6, 2015, 10:28:01 AM7/6/15
to
On Monday, July 6, 2015 at 10:16:02 AM UTC-4, Jens Thoms Toerring wrote:
> What would be an sprintf() variant be useful that doens't write
> a trailing '\0'? The only reason I can think of is that you want
> to write to some memory location of restricted size and the trai-
> ling '\0' is one too much. But in that case you can use snprintf()
> to ensure that no more that the number of chars you want get out-
> put, i.e. in your example
>
> snprint( buffer, 4, "%04u\n", value );
>
> would probably do the trick (this, of course, assumes that 'value'
> never is larger than 9999).

As I understand it, snprintf() still puts the trailing NULL.

Reinhardt Behm

unread,
Jul 6, 2015, 10:34:31 AM7/6/15
to
When writing "%04u\0" the compiler interprets the \0 as a NUL character and
appends one as the string delimiter.

> In order to have the sam, you'd need:
>
> char *fmt1[] = { '%', '0', '4', 'u', '\', '0', '\0' }
For that you would have to write "%04u\\0".

But it is not about how you write it. It about how the function could
interpret your format string. If it interprets it as a normal C null-
terminated string the second NUL-character will not be seen, because the
first you explicitly put in there will be the delimiter.
In this sense the two format strings are equivalent from the functions point
of view.

If you want it to be interpreted otherwise, you must tell the function the
length of the string. This would be incompatible with all C conventions.

>
> ...physically embedding the \ and 0 as separate characters, then
> the trailing null.
>
>> For the rare case you can always write your above example as
>> char buffer[8];
>> char buffer1[sizeof(buffer) + 1];
>> sprintf(buffer1, "%04u%02%02u", year, month, day);
>> strncpy(buffer, buffer1, sizeof(buffer));
>
> Yes. I'm thinking this double-processing could be avoided with
> rawprintf(). Isn't C all about speed? I mean even the type for
> int is supposed to be fast. Why not a common operation like
> preparing an output string for disk, display, network traffic,
> or future processing?

Is this such a common requirement? When you create strings you usually want
to handle them with normal string functions. These will all not work with
you non-delimited "strings".
So you would have to use these always with additional length information.
That's also easy when using the normal s(n)printf with a destination buffer
one longer than needed and passing the appropriate length argument
(bufferlen -1).

--
Reinhardt

Bartc

unread,
Jul 6, 2015, 10:47:08 AM7/6/15
to
On 06/07/2015 15:12, Rick C. Hodgin wrote:
> On Monday, July 6, 2015 at 9:58:43 AM UTC-4, Reinhardt Behm wrote:

>> For the rare case you can always write your above example as
>> char buffer[8];
>> char buffer1[sizeof(buffer) + 1];
>> sprintf(buffer1, "%04u%02%02u", year, month, day);
>> strncpy(buffer, buffer1, sizeof(buffer));
>
> Yes. I'm thinking this double-processing could be avoided with
> rawprintf(). Isn't C all about speed?

Having a duplicate set of printf functions that don't append nul aren't
going to be that useful.

Usually, you will not know that size of the resulting string, which
means providing a buffer with spare capacity. One of those spare bytes
will have a zero inserted; just ignore it (or set it back to a space or
whatever the original was).

If you do know the length of the result, as in the above example, then
snprintf() will not append the nul, if the buffer provided is exactly
the write size.

I don't think that leaves many other examples where such a duplicate set
of functions as you propose would be worthwhile adding.

--
Bartc


Keith Thompson

unread,
Jul 6, 2015, 10:56:35 AM7/6/15
to
j...@toerring.de (Jens Thoms Toerring) writes:
[...]
> What would be an sprintf() variant be useful that doens't write
> a trailing '\0'? The only reason I can think of is that you want
> to write to some memory location of restricted size and the trai-
> ling '\0' is one too much. But in that case you can use snprintf()
> to ensure that no more that the number of chars you want get out-
> put, i.e. in your example
>
> snprint( buffer, 4, "%04u\n", value );
>
> would probably do the trick (this, of course, assumes that 'value'
> never is larger than 9999).

snprintf always writes a trailing null character. In this case, the
output will be truncated to 3 digits (and snprintf will return 4 to
indicate how many characters would have been written if there were
enough room).

Replying to other articles in this thread, NULL is a null *pointer*
constant, and should not be used to refer to the null character.

--
Keith Thompson (The_Other_Keith) ks...@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"

Keith Thompson

unread,
Jul 6, 2015, 11:06:50 AM7/6/15
to
Bartc <b...@freeuk.com> writes:
[...]
> If you do know the length of the result, as in the above example, then
> snprintf() will not append the nul, if the buffer provided is exactly
> the write size.

That's incorrect; snprintf always writes a terminating null
character to the target array (unless n==0 and it doesn't write
anything at all). It will truncate the output if necesesary to leave
room for the '\0'. N1570 7.21.6.5p2:

The snprintf function is equivalent to fprintf, except that
the output is written into an array (specified by argument s)
rather than to a stream. If n is zero, nothing is written, and
s may be a null pointer. Otherwise, output characters beyond
the n-1st are discarded rather than being written to the array,
and a null character is written at the end of the characters
actually written into the array. If copying takes place between
objects that overlap, the behavior is undefined.

Joe Pfeiffer

unread,
Jul 6, 2015, 11:32:23 AM7/6/15
to
"Rick C. Hodgin" <rick.c...@gmail.com> writes:

> Two questions:
>
> (1) Is there a version of sprintf() without a trailing NULL?

Not that I'm aware of.

> (2) Why does sprintf() automatically insert a trailing NULL?

Because it's a string function. C doesn't have a native "string" type,
but the standard library implements them. To the best of my knowledge,
the only string function that doesn't maintain the trailing null byte is
strncpy(), whose behavior is different enough from the rest that it
could be argued it isn't a string function.

Malcolm McLean

unread,
Jul 6, 2015, 11:38:03 AM7/6/15
to
On Monday, July 6, 2015 at 2:17:17 PM UTC+1, Rick C. Hodgin wrote:
> Two questions:
>
> (1) Is there a version of sprintf() without a trailing NULL?
> (2) Why does sprintf() automatically insert a trailing NULL?
>
Kind of. You can use strncpy(), which doesn't add a trailing null. But you can't
pass parameters to it.
strncpy was designed for writing to database structures which typically had fixed
fields, which weren't nil-terminated unless they needed right padding.
sprintf() was designed as a safe replacement for sprint(), which creates a
string intended for use by other C functions. If it's not nul-terminated, it's
not safe to pass about.

Ben Bacarisse

unread,
Jul 6, 2015, 11:38:32 AM7/6/15
to
"Rick C. Hodgin" <rick.c...@gmail.com> writes:

You are right it isn't but not for the reason you state. Reinhardt has
a typo -- there's an unintended * in the type.

What you wrote ("%04u\0") denotes (char)[]{'%', '0', '4', 'u', 0, 0}.
There's not way for sprintf to tell the difference between the null you
put there explicitly and the one that ends a string literal.

> The '\0' is interpreted by the compiler as a NULL.

'\0' is the same as 0 and denotes character called null.

> In order to have the sam, you'd need:
>
> char *fmt1[] = { '%', '0', '4', 'u', '\', '0', '\0' }
>
> ...physically embedding the \ and 0 as separate characters, then
> the trailing null.

That's not even valid C (even when the pointer is removed from the
declaration). You might have meant '\\' in the initialiser, but that's
just as bad as adding a null character.

sprintf already has the right mechanism -- the % syntax. You could
propose a new conversion specifier which, like %%, denotes a single
character -- a null. It would mainly be useful in your memprintf
function:

memprintf(buffer, "%4u%!", year); // terminated -- min 5 bytes written
memprintf(buffer, "%4u%!", year); // min 4 bytes written, no null

>> For the rare case you can always write your above example as
>> char buffer[8];
>> char buffer1[sizeof(buffer) + 1];
>> sprintf(buffer1, "%04u%02%02u", year, month, day);
>> strncpy(buffer, buffer1, sizeof(buffer));
>
> Yes. I'm thinking this double-processing could be avoided with
> rawprintf().

You can avoid the double copy (in many cases) like this:

union {
char unterminated[8];
char string[8 + 1];
} buffer;
sprintf(buffer.string, "%04u%02u%02u", year, month, day);

and then use buffer.unterminated with its helpfully worrying name.
Alternatively, you could write:

char buffer[8];
snprintf(buffer, sizeof buffer, "%04u%02u%02u", year, month, day);
buffer[sizeof buffer - 1] = '0' + day % 10;

which is not very nice, but it's safer all round because you can't
overflow the buffer due to bad values in the printed variables.

<snip>
--
Ben.

Rick C. Hodgin

unread,
Jul 6, 2015, 11:44:10 AM7/6/15
to
Quite correct. My mistake.

> > ...physically embedding the \ and 0 as separate characters, then
> > the trailing null.
> >
> >> For the rare case you can always write your above example as
> >> char buffer[8];
> >> char buffer1[sizeof(buffer) + 1];
> >> sprintf(buffer1, "%04u%02%02u", year, month, day);
> >> strncpy(buffer, buffer1, sizeof(buffer));
> >
> > Yes. I'm thinking this double-processing could be avoided with
> > rawprintf(). Isn't C all about speed? I mean even the type for
> > int is supposed to be fast. Why not a common operation like
> > preparing an output string for disk, display, network traffic,
> > or future processing?
>
> Is this such a common requirement? When you create strings you usually want
> to handle them with normal string functions. These will all not work with
> you non-delimited "strings".
> So you would have to use these always with additional length information.
> That's also easy when using the normal s(n)printf with a destination buffer
> one longer than needed and passing the appropriate length argument
> (bufferlen -1).

Yes, it's a common requirement. I use sprintf() to build things
from variable data for various purposes. Having the trailing null
is useful at times, but other times it is not useful, and there
doesn't seem to be a way to bypass it without doing double the
memory work.

Rick C. Hodgin

unread,
Jul 6, 2015, 11:54:00 AM7/6/15
to
I think I will just write my own. I'll have to eventually anyway.

I don't use the full set of printf format specifiers, and will add
the ones I need as they come up. I've also always had a dream of
using sprintf(buffer, "%3s", "abcdef"); and having it only copy
three characters, but I think I saw today in a google search where
using sprintf(buffer, "%.*s", 3, "abcdef"); would work. I like that,
but would prefer the %3s syntax. Testing it just now it works in
Visual Studio and GCC 5.1.

Keith Thompson

unread,
Jul 6, 2015, 11:54:19 AM7/6/15
to
What is "sprint()"? Are you thinking of snprintf(), which was designed
as a safe(r) replacement for sprintf()?

Rick C. Hodgin

unread,
Jul 6, 2015, 12:00:42 PM7/6/15
to
^^

The %.*s uses the passed parameter. Using %.3s uses the hard-coded length.

Ben Bacarisse

unread,
Jul 6, 2015, 12:14:11 PM7/6/15
to
"Rick C. Hodgin" <rick.c...@gmail.com> writes:
<snip>
> I think I will just write my own. I'll have to eventually anyway.
>
> I don't use the full set of printf format specifiers, and will add
> the ones I need as they come up. I've also always had a dream of
> using sprintf(buffer, "%3s", "abcdef"); and having it only copy
> three characters, but I think I saw today in a google search where
> using sprintf(buffer, "%.*s", 3, "abcdef"); would work. I like that,
> but would prefer the %3s syntax.

That syntax already means something, so you are ditching an existing
meaning to get the one you want. Obviously, for you own provate
language the choice is yours, but it would be a bad choice for something
that people familiar with C (or any other language that shares C's
printf specifiers) would have to learn. (And that's a lot of people!)

--
Ben.

Rick C. Hodgin

unread,
Jul 6, 2015, 12:24:25 PM7/6/15
to
What does "%3s" already mean? When I use it it just appears to ignore
the 3 and do a %s.

Rick C. Hodgin

unread,
Jul 6, 2015, 12:31:46 PM7/6/15
to
On this page:

http://www.cplusplus.com/reference/cstdio/printf/

...I see that it indicates the minimum number of characters to print.
Okay. I've used that with numbers, but never with a string. I can
use the %.3s syntax for explicit copy width.

Bartc

unread,
Jul 6, 2015, 12:42:59 PM7/6/15
to
On 06/07/2015 16:06, Keith Thompson wrote:
> Bartc <b...@freeuk.com> writes:
> [...]
>> If you do know the length of the result, as in the above example, then
>> snprintf() will not append the nul, if the buffer provided is exactly
>> the write size.
>
> That's incorrect; snprintf always writes a terminating null
> character to the target array (unless n==0 and it doesn't write
> anything at all). It will truncate the output if necesesary to leave
> room for the '\0'. N1570 7.21.6.5p2:
>
> The snprintf function is equivalent to fprintf, except that
> the output is written into an array (specified by argument s)
> rather than to a stream. If n is zero, nothing is written, and
> s may be a null pointer. Otherwise, output characters beyond
> the n-1st are discarded rather than being written to the array,
> and a null character is written at the end of the characters
> actually written into the array. If copying takes place between
> objects that overlap, the behavior is undefined.
>

MS' doc say otherwise:
-----------------------
Let len be the length of the formatted data string, not including the
terminating null. len and count are in bytes for _snprintf, wide
characters for _snwprintf.

If len < count, len characters are stored in buffer, a null-terminator
is appended, and len is returned.

If len = count, len characters are stored in buffer, no null-terminator
is appended, and len is returned.

If len > count, count characters are stored in buffer, no
null-terminator is appended, and a negative value is returned.
-----------------------

But most C compilers behave as you suggest. I was hoping to be able to
run MSVC in the form of CL.EXE (/this/ is the sort of reason I wanted it
around, but it's not worth 6-11GB of disk space!), but it failed trying
to link in snprintf(). And I've no idea how to fix it.

I think Rick uses MSVC and that might have been a (non-portable)
solution if it behaved as its docs say.

However, TCC (Tiny C Compiler) appears to behave like MS says it does.
The following program prints 49 50 0 99... on most compilers, but 49 50
51 99 ... with TCC:

#include <stdio.h>

int main(void) {
char str[]={99,99,99,99,99,99,99,99};
int i;

snprintf(str,3,"%3d",12345);

for (i=0; i<sizeof(str); ++i)
printf("%d ",str[i]);
}

--
Bartc

Jens Thoms Toerring

unread,
Jul 6, 2015, 12:46:28 PM7/6/15
to
Rick C. Hodgin <rick.c...@gmail.com> wrote:
Uups, sorry, you're correct. I'm obviously still suffering from
the heat wave of the last days, brains processing speed still
reduced due to overheating;-)

Rick C. Hodgin

unread,
Jul 6, 2015, 12:48:49 PM7/6/15
to
On Monday, July 6, 2015 at 12:42:59 PM UTC-4, Bart wrote:
> I think Rick uses MSVC and that might have been a (non-portable)
> solution if it behaved as its docs say.

I use Visual Studio and MSVC for 99.5% of development, and then MSVC
and GCC for testing and final debugging on shipped products for most
everything these days, making sure my code compiles in both (at least
once every several days that is, as the only development I do outside
of Visual Studio and MSVC is when I'm fixing up code that doesn't
work in GCC).

Bartc

unread,
Jul 6, 2015, 1:08:03 PM7/6/15
to
On 06/07/2015 17:42, Bartc wrote:

> If len = count, len characters are stored in buffer, no null-terminator
> is appended, and len is returned.
>
> If len > count, count characters are stored in buffer, no
> null-terminator is appended, and a negative value is returned.
> -----------------------
>
> But most C compilers behave as you suggest. I was hoping to be able to
> run MSVC in the form of CL.EXE (/this/ is the sort of reason I wanted it
> around, but it's not worth 6-11GB of disk space!), but it failed trying
> to link in snprintf(). And I've no idea how to fix it.

The 'fix' seems to be to call _snprintf() instead (I thought the "_"
prefix was something prepended to all global names, something to do with
how linking works.)

Anyway, MSVC doesn't add the terminator in the above circumstances, but
it also seems this is not standard behaviour and that it might have
changed in the most recent version!

--
Bartc

Martin Shobe

unread,
Jul 6, 2015, 1:40:28 PM7/6/15
to
On 7/6/2015 12:07 PM, Bartc wrote:
> On 06/07/2015 17:42, Bartc wrote:
>
>> If len = count, len characters are stored in buffer, no null-terminator
>> is appended, and len is returned.
>>
>> If len > count, count characters are stored in buffer, no
>> null-terminator is appended, and a negative value is returned.
>> -----------------------
>>
>> But most C compilers behave as you suggest. I was hoping to be able to
>> run MSVC in the form of CL.EXE (/this/ is the sort of reason I wanted it
>> around, but it's not worth 6-11GB of disk space!), but it failed trying
>> to link in snprintf(). And I've no idea how to fix it.
>
> The 'fix' seems to be to call _snprintf() instead (I thought the "_"
> prefix was something prepended to all global names, something to do with
> how linking works.)

Microsoft does do that to indicate the calling convention for 32-bit
programs, however, the rules for that apply to _snprintf() as well, so,
if I remember the rules correctly, the identifier _snprintf maps to the
symbol __snprintf (two leading underscores).

> Anyway, MSVC doesn't add the terminator in the above circumstances, but
> it also seems this is not standard behaviour and that it might have
> changed in the most recent version!

It's not standard behavior (Microsoft doesn't claim it conforms either),
and I don't think it has changed recently.

Martin Shobe

Keith Thompson

unread,
Jul 6, 2015, 1:47:01 PM7/6/15
to
Bartc <b...@freeuk.com> writes:
> On 06/07/2015 16:06, Keith Thompson wrote:
>> Bartc <b...@freeuk.com> writes:
>> [...]
>>> If you do know the length of the result, as in the above example, then
>>> snprintf() will not append the nul, if the buffer provided is exactly
>>> the write size.
>>
>> That's incorrect; snprintf always writes a terminating null
>> character to the target array (unless n==0 and it doesn't write
>> anything at all). It will truncate the output if necesesary to leave
>> room for the '\0'. N1570 7.21.6.5p2:
>>
>> The snprintf function is equivalent to fprintf, except that
>> the output is written into an array (specified by argument s)
>> rather than to a stream. If n is zero, nothing is written, and
>> s may be a null pointer. Otherwise, output characters beyond
>> the n-1st are discarded rather than being written to the array,
>> and a null character is written at the end of the characters
>> actually written into the array. If copying takes place between
>> objects that overlap, the behavior is undefined.
>>
>
> MS' doc say otherwise:
> -----------------------
> Let len be the length of the formatted data string, not including the
> terminating null. len and count are in bytes for _snprintf, wide
> characters for _snwprintf.

The document you're quoting is for something called _snprintf, which is
a distinct function, with a different name, from the standard snprintf.

(snprintf was added to C by the 1999 standard. I think Microsoft
invented its own non-standard version before that.)

[...]

> But most C compilers behave as you suggest. I was hoping to be able to
> run MSVC in the form of CL.EXE (/this/ is the sort of reason I wanted it
> around, but it's not worth 6-11GB of disk space!), but it failed trying
> to link in snprintf(). And I've no idea how to fix it.

*All* C implementations (for C99 or later) behave like that. Any that
don't are not C implementations.

> I think Rick uses MSVC and that might have been a (non-portable)
> solution if it behaved as its docs say.
>
> However, TCC (Tiny C Compiler) appears to behave like MS says it does.
> The following program prints 49 50 0 99... on most compilers, but 49 50
> 51 99 ... with TCC:
>
> #include <stdio.h>
>
> int main(void) {
> char str[]={99,99,99,99,99,99,99,99};
> int i;
>
> snprintf(str,3,"%3d",12345);
>
> for (i=0; i<sizeof(str); ++i)
> printf("%d ",str[i]);
> }

If so, then tcc is non-conforming. But I just tried it with tcc version
0.9.25 on Linux Mint, and it behaves correctly.

As far as I can tell, tcc uses the system's C library. snprintf is
implemented by the library, not by the compiler. It may be that tcc is
using a non-conforming C library on your system.

Bartc

unread,
Jul 6, 2015, 1:59:05 PM7/6/15
to
On 06/07/2015 18:46, Keith Thompson wrote:
> Bartc <b...@freeuk.com> writes:

>> However, TCC (Tiny C Compiler) appears to behave like MS says it does.
>> The following program prints 49 50 0 99... on most compilers, but 49 50
>> 51 99 ... with TCC:
>>
>> #include <stdio.h>
>>
>> int main(void) {
>> char str[]={99,99,99,99,99,99,99,99};
>> int i;
>>
>> snprintf(str,3,"%3d",12345);
>>
>> for (i=0; i<sizeof(str); ++i)
>> printf("%d ",str[i]);
>> }
>
> If so, then tcc is non-conforming. But I just tried it with tcc version
> 0.9.25 on Linux Mint, and it behaves correctly.
>
> As far as I can tell, tcc uses the system's C library. snprintf is
> implemented by the library, not by the compiler. It may be that tcc is
> using a non-conforming C library on your system.

I was using 0.9.26. But on Linux, it behaves like other compilers.

So it's more than likely that it's using MS' C runtime library, but
without the need to call _snprintf instead. I suppose that's one way of
keeping it tiny.

--
Bartc

Richard Bos

unread,
Jul 6, 2015, 3:54:20 PM7/6/15
to
"Rick C. Hodgin" <rick.c...@gmail.com> wrote:

> On Monday, July 6, 2015 at 10:16:02 AM UTC-4, Jens Thoms Toerring wrote:
> > Rick C. Hodgin <rick.c...@gmail.com> wrote:
> > > Or at this point in support of legacy systems, a new function:
> > > rawprintf(buffer, "%04u\0", value); // No NULL-termination by default
> >
> > because the 's' at the start of sprintf() stands for "string"
> > and strings are strings because the have a'\0' at the end -
> > otherwise it would be just a char array.
>
> Agreed. So why isn't there a version like printf() which doesn't
> put a trailing NULL the way sprintf() does, but one that does so
> to a memory block?

For the same reason that there isn't a version of fopen() which
automatically puts ".txt" at the end of all your filenames: it's going
to be of extremely limited use, so limited that it has no job in any
general library let alone the Standard one.

Richard

Rick C. Hodgin

unread,
Jul 6, 2015, 4:31:44 PM7/6/15
to
On Monday, July 6, 2015 at 3:54:20 PM UTC-4, Richard Bos wrote:
> "Rick C. Hodgin" <rick.c...@gmail.com> wrote:
>
> > On Monday, July 6, 2015 at 10:16:02 AM UTC-4, Jens Thoms Toerring wrote:
> > > Rick C. Hodgin <rick.c...@gmail.com> wrote:
> > > > Or at this point in support of legacy systems, a new function:
> > > > rawprintf(buffer, "%04u\0", value); // No NULL-termination by default
> > >
> > > because the 's' at the start of sprintf() stands for "string"
> > > and strings are strings because the have a'\0' at the end -
> > > otherwise it would be just a char array.
> >
> > Agreed. So why isn't there a version like printf() which doesn't
> > put a trailing NULL the way sprintf() does, but one that does so
> > to a memory block?
>
> For the same reason that there isn't a version of fopen() which
> automatically puts ".txt" at the end of all your filenames: it's going
> to be of extremely limited use, so limited that it has no job in any
> general library let alone the Standard one.
>
> Richard

I don't really see how it could be of limited use. The sprintf()
function has exceeding utility. It is only in this one area where
it forces a trailing NULL that it is an issue. And in googling
today to try to find a solution, I was able to find where several
people had asked the same question. I'm guessing others had the
same question, but associated the leading "s" with string and just
concluded it would add a trailing null without wishing too hard
for the one that didn't because you could work around it quite
easily with a copy operation.

I can just see wrapping all of the printf-related functions into
a single function with a few flags, and one of them being "include
a trailing null" and that being that. A tiny up-front wrapper
that passes false in on memprintf() and true on sprintf(). Seems
so simple that it would've been worth the tiny effort by the C
devs back in the day.

Richard Heathfield

unread,
Jul 6, 2015, 4:52:28 PM7/6/15
to
On 06/07/15 21:31, Rick C. Hodgin wrote:
> On Monday, July 6, 2015 at 3:54:20 PM UTC-4, Richard Bos wrote:
>> "Rick C. Hodgin" <rick.c...@gmail.com> wrote:
>>
<snip>
>> >
>> > [...] So why isn't there a version like printf() which doesn't
>> > put a trailing NULL the way sprintf() does, but one that does so
>> > to a memory block?
>>
>> For the same reason that there isn't a version of fopen() which
>> automatically puts ".txt" at the end of all your filenames: it's going
>> to be of extremely limited use, so limited that it has no job in any
>> general library let alone the Standard one.
>
> I don't really see how it could be of limited use. The sprintf()
> function has exceeding utility. It is only in this one area where
> it forces a trailing NULL that it is an issue.

First, a terminology point. In C, "NULL" refers to a macro that expands
to an implementation-defined null pointer constant. The character that
terminates a string is known as a "null character".

Now, to the substance of your point. C supports two distinct kinds of
data (one of which is a subset of the other): completely arbitrary
(which we normally refer to as 'binary'), and text. The text format is
clearly intended to be human-readable, whereas binary is not quite so
friendly. Although there is no hard-and-fast rule about it, we generally
think of some functions as being suitable for manipulating text, and
others as suitable for manipulating binary data. For example, fopen(fn,
"r") is generally used when we expect the data file to contain
human-readable text, and fopen(fn, "rb") when we expect arbitrary data.
We use fgets, fprintf, and so on for reading and writing text, and
fread, fwrite for reading and writing binary data. We use mem* for
manipulating binary data, and str* for manipulating strings of
human-readable text. The whole idea of strings is all to do with
human-readable text. That doesn't mean we can't write programs that read
text files produced by another program and perform processing based on
that input, or that we can't write programs that produce text output
intended for input to another program. But, throughout, the assumption
is that *at some point* a human may have to read this stuff, so we keep
to printable, readable characters rather than arbitrary bit patterns.
The null character is an intrinsic part of the C string model.

When we start to confuse these two ideas, text and binary, we are likely
to run into problems - not necessarily insurmountable problems, but
problems nonetheless.

Formatted output is generally intended for human-readable text, and so
it makes sense to provide printf, fprintf, sprintf, and so on. These
functions work with strings because strings are how our programs
interact with humans. And, because they work with strings, they use null
characters to terminate those strings.

The mem* functions are generally used for manipulating binary data. To
provide a memprintf function would be to confuse the two models, to
little if any gain (as far as I can recall, you're the first person I've
encountered in comp.lang.c who has ever expressed a desire for a
memprintf function), and at the considerable cost of blurring the
distinction between binary data and text data.

In short, the game isn't worth the candle.

> And in googling
> today to try to find a solution, I was able to find where several
> people had asked the same question.

Were any of these people asking in comp.lang.c? I don't recall such a
thread, but then it's been quite a while since I read every single
article posted on comp.lang.c, so I may have missed it.

> I can just see wrapping all of the printf-related functions into
> a single function with a few flags, and one of them being "include
> a trailing null" and that being that. A tiny up-front wrapper
> that passes false in on memprintf() and true on sprintf(). Seems
> so simple that it would've been worth the tiny effort by the C
> devs back in the day.

Such a change would fill a much-needed gap.

--
Richard Heathfield
Email: rjh at cpax dot org dot uk
"Usenet is a strange place" - dmr 29 July 1999
Sig line 4 vacant - apply within

Ben Bacarisse

unread,
Jul 6, 2015, 6:45:00 PM7/6/15
to
Ben Bacarisse <ben.u...@bsb.me.uk> writes:
<snip>
>>> >> Rick C. Hodgin <rick.c...@gmail.com> wrote:
>>> >> > (1) Is there a version of sprintf() without a trailing NULL?
<snip>
> sprintf already has the right mechanism -- the % syntax. You could
> propose a new conversion specifier which, like %%, denotes a single
> character -- a null. It would mainly be useful in your memprintf
> function:
>
> memprintf(buffer, "%4u%!", year); // terminated -- min 5 bytes written
> memprintf(buffer, "%4u%!", year); // min 4 bytes written, no null

A thought occurs (it does happen)...
You could implement what you want as an extension. Since a trailing %
in the format is undefined behaviour, you could have a version of
sprintf that interprets such an invalid conversion specifier as a
request to omit the trailing null character.

<snip>
--
Ben.

Rick C. Hodgin

unread,
Jul 6, 2015, 6:49:18 PM7/6/15
to
Ben Bacarisse wrote:
> A thought occurs (it does happen)... You could
> implement what you want as an extension.
> Since a trailing % in the format is undefined
> behaviour, you could have a version of sprintf
> that interprets such an invalid conversion
> specifier as a request to omit the trailing null
> character.

I love it.

Richard Bos

unread,
Jul 6, 2015, 6:53:30 PM7/6/15
to
"Rick C. Hodgin" <rick.c...@gmail.com> wrote:

> On Monday, July 6, 2015 at 3:54:20 PM UTC-4, Richard Bos wrote:
> > "Rick C. Hodgin" <rick.c...@gmail.com> wrote:
> >
> > > Agreed. So why isn't there a version like printf() which doesn't
> > > put a trailing NULL the way sprintf() does, but one that does so
> > > to a memory block?
> >
> > For the same reason that there isn't a version of fopen() which
> > automatically puts ".txt" at the end of all your filenames: it's going
> > to be of extremely limited use, so limited that it has no job in any
> > general library let alone the Standard one.
>
> I don't really see how it could be of limited use.

I don't see how it could be of any real use at all, let alone meaningful
use enough to incorporate it into the Standard.

Once again the question to you is: _WHY_ the fsck do you want this!? And
more importantly: _WHY_ should the rest of us pay the price?

Richard

Sjouke Burry

unread,
Jul 6, 2015, 7:48:33 PM7/6/15
to
On 06.07.15 15:16, Rick C. Hodgin wrote:
> Two questions:
>
> (1) Is there a version of sprintf() without a trailing NULL?
> (2) Why does sprintf() automatically insert a trailing NULL?
>
> TYIA!
>
> Best regards,
> Rick C. Hodgin
>
Look at the definition of a string.
sprintf produces a string, with all the trimmings needed for a string.
What is it you dont understand about strings?

Ben Bacarisse

unread,
Jul 6, 2015, 8:04:28 PM7/6/15
to
I agree that adding a new suite of functions for this small feature
looks like overkill but there need be no price if it's done through the
existing functions (sprintf and friends).

As I posted elsewhere, the standard could mandate that something
currently undefined (a trailing % being the obvious choice) causes the
function to omit the null.

--
Ben.

Rick C. Hodgin

unread,
Jul 6, 2015, 8:10:08 PM7/6/15
to
Sjouke Burry wrote:
> Rick C. Hodgin wrote:
>> Two questions:
>> (1) Is there a version of sprintf() without a trailing NULL?
>> (2) Why does sprintf() automatically insert a trailing NULL?
>> TYIA!
>> Best regards,
>> Rick C. Hodgin
>
> Look at the definition of a string. sprintf
> produces a string, with all the trimmings
> needed for a string. What is it you don't
> understand about strings?

Multi-threading.

Malcolm McLean

unread,
Jul 6, 2015, 10:13:53 PM7/6/15
to
On Monday, July 6, 2015 at 11:53:30 PM UTC+1, Richard Bos wrote:
> "Rick C. Hodgin" <rick.c...@gmail.com> wrote:
>
> > On Monday, July 6, 2015 at 3:54:20 PM UTC-4, Richard Bos wrote:
> > > "Rick C. Hodgin" <rick.c...@gmail.com> wrote:
> > >
> > > > Agreed. So why isn't there a version like printf() which doesn't
> > > > put a trailing NULL the way sprintf() does, but one that does so
> > > > to a memory block?
> > >
> > > For the same reason that there isn't a version of fopen() which
> > > automatically puts ".txt" at the end of all your filenames: it's going
> > > to be of extremely limited use, so limited that it has no job in any
> > > general library let alone the Standard one.
> >
> > I don't really see how it could be of limited use.
>
> I don't see how it could be of any real use at all, let alone meaningful
> use enough to incorporate it into the Standard.
>
> Once again the question to you is: _WHY_ the fsck do you want this!? And
> more importantly: _WHY_ should the rest of us pay the price?
>
It's useful if you're mixing and matching C with another language.
Most languages do in fact use aciiz strings because of influence from
C, but we can easily imagine one which passes string about as
character buffer / length. Adding a terminating nul means that all
your buffers are a byte over length, which could cause subtle bugs
down the line, for example when the string is exactly a power of two
and the language X internal incorrectly assumes that it's a single
memory block.


Joe Pfeiffer

unread,
Jul 6, 2015, 11:08:34 PM7/6/15
to
"Rick C. Hodgin" <rick.c...@gmail.com> writes:

I've got *no* idea what your no-trailing-zero strings have to do with
multi-threading... explain, please?

Rick C. Hodgin

unread,
Jul 7, 2015, 12:29:12 AM7/7/15
to
Joe Pfeiffer wrote:
> I've got *no* idea what your no-trailing-zero
> strings have to do with multi-threading...
> explain, please?

It loses something when explained. But... string,
threads... Threads make up a string... Multi-threading...
like sewing threads, combined into a string, something
you might use to play with the cat, or hold some
meat from the butcher wrapped up.

Humor. It loses something when explained.

Reinhardt Behm

unread,
Jul 7, 2015, 12:45:19 AM7/7/15
to
And this problem does not exist, when your not nul-terminated string is by
itself just a byte larger?

But nobody keeps you from creating your own special functions for this. The
majority of other C programmers might just not need it.

--
Reinhardt

Rosario19

unread,
Jul 7, 2015, 4:15:41 AM7/7/15
to
yes...
a=sprintf(b, etc); if(a<0) Error();
b[a] has to be 0

Bartc

unread,
Jul 7, 2015, 5:31:30 AM7/7/15
to
A compiled language using counted strings would have a *lot* of problems
if it relied too much on C string functions. But it would be its
responsibility to fix, not C's.

My own compiled language uses zero-terminated strings because it's
closely allied to C, and it's a simple solution at this level of language.

My interpreted language has counted, managed strings. There is less
interaction with C libraries, but sprintf generating an extra zero is
not one of the problems (mainly, because it uses own tostring()
functions; but where sprintf() /is/ called, it just uses a buffer with
plenty of spare space for the zero to be added! And then ignored.)

It's more of a problem having to 'add' a zero-terminator when calling a
C or OS function, but I don't expect C or the OS to provide a parallel
set of functions for that purpose!

--
Bartc

Malcolm McLean

unread,
Jul 7, 2015, 5:51:57 AM7/7/15
to
On Tuesday, July 7, 2015 at 10:31:30 AM UTC+1, Bart wrote:
> On 07/07/2015 03:13, Malcolm McLean wrote:
>
> A compiled language using counted strings would have a *lot* of problems
> if it relied too much on C string functions. But it would be its
> responsibility to fix, not C's.
>
No, it would be C's responsibility.
The C callable interface to language X could be
4 bytes big-endian length
N bytes character data

C has to set out memory in that layout.
If we further define that a string array is a set of string concatenated in memory,
then you've got problems with the nul (but only if the length goes above 0xFFFFFF).
>
> It's more of a problem having to 'add' a zero-terminator when calling a
> C or OS function, but I don't expect C or the OS to provide a parallel
> set of functions for that purpose!
>
I agree. But language X could well provide an
XstringtoCstring keyword. That's a matter for language X, but if the designers
expect users to have to call a lot of C-convention functions, it would be an obvious
thing to do.

Bartc

unread,
Jul 7, 2015, 7:11:09 AM7/7/15
to
On 07/07/2015 10:51, Malcolm McLean wrote:
> On Tuesday, July 7, 2015 at 10:31:30 AM UTC+1, Bart wrote:
>> On 07/07/2015 03:13, Malcolm McLean wrote:
>>
>> A compiled language using counted strings would have a *lot* of problems
>> if it relied too much on C string functions. But it would be its
>> responsibility to fix, not C's.
>>
> No, it would be C's responsibility.
> The C callable interface to language X could be
> 4 bytes big-endian length
> N bytes character data
>
> C has to set out memory in that layout.
> If we further define that a string array is a set of string concatenated in memory,
> then you've got problems with the nul (but only if the length goes above 0xFFFFFF).

There are a hundred languages that C could interface to, each with its
own string handling schemes. It would unreasonable to expect C to
accommodate all of them.

There would also be less reason for C to care about arbitrary language
X, than for X to care about C (since a lot of interfaces and API are
defined via C).

So it is a problem for X, unless X is important and well-known enough.
(For example, by C having special provision for calling foreign
functions in Fortran or Pascal.) But usually C is oblivious to any other
language.

(When I work on a language X, I need to have attributes such 'clang' and
'windows' for foreign functions, and sometimes types such as 'cstring'.
I doubt C will be similarly accommodating!)

--
Bartc

Malcolm McLean

unread,
Jul 7, 2015, 8:09:14 AM7/7/15
to
On Tuesday, July 7, 2015 at 12:11:09 PM UTC+1, Bart wrote:
> On 07/07/2015 10:51, Malcolm McLean wrote:
>
> > C has to set out memory in that layout.
> > If we further define that a string array is a set of string concatenated in memory,
> > then you've got problems with the nul (but only if the length goes above 0xFFFFFF).
>
> There are a hundred languages that C could interface to, each with its
> own string handling schemes. It would unreasonable to expect C to
> accommodate all of them.
>
C allows you to set up arbitrary binary structures in memory. As long as language X
has an interface defined in terms of such structures, we can write C functions to call
it.
However it's handy to have support for creating a non-nul terminated character buffer.
That's quite a common requirement for interfacing.

Bartc

unread,
Jul 7, 2015, 8:52:45 AM7/7/15
to
There is, in the form of char[] types and mem-functions.

That leaves a few examples such as sprintf, but it's not hard to work
around the extra 0 byte that is generated, without a whole new set of
functions.

And suppose the string format had a byte-count as the first character,
or at the end? These requirements are too specialised.

(I sometimes use a string format that consists of a fixed field of N
characters, is not zero-terminated, accommodates a string up to N
characters, *and* it includes a count! But I don't expect a version of
sprintf to directly write into such a buffer for me.)

--
Bartc

Gordon Burditt

unread,
Jul 7, 2015, 4:40:54 PM7/7/15
to
> I don't really see how it could be of limited use.

Neither do I: I say that if it were available, you shouldn't
use it, on the grounds of runtime efficiency.

Many people avoid functions that produce non-nul-terminated non-strings
on the grounds that it's a programming error (and possible security
problem) waiting to happen. (see also strncpy(). Even though
strncpy() does have a use - copying a string to a character array
of known size, where if it's of maximum length there is no NUL
terminator. Were it not for the historical format of UNIX directories
(14-character array for the name), I'd recommend never having
included strncpy() in standard C. )

> The sprintf()
> function has exceeding utility.
> It is only in this one area where
> it forces a trailing NULL that it is an issue.

WHY is it an issue? You like wasting memory to save 1 byte in a
buffer? You like wasting lots of memory read accesses to avoid 1
write? Is there also an issue that there is no variant of sprintf()
that appends a euro symbol to the string without being asked?

What do you gain?
- It saves 1 character of buffer size to hold the '\0'.
- It saves one memory access to store the '\0'.

What do you lose?
- You need at least 1, and probably 2 extra characters
in the format string, or else you need to pass an
additional argument, which is likely to take 4 bytes..
- If you use the variant function, it takes memory
accesses to interpret the additional characters in the
format string.
- You have an additioal function that occupies memory.
- If you have a common subroutine, or one calls the other,
you have 1 additional function call overhead, with one
additional argument passed, and you probably end up
adding this overhead to sprintf() as well as the new
function.

> concluded it would add a trailing null without wishing too hard
> for the one that didn't because you could work around it quite
> easily with a copy operation.

Usually you can work around it much more easily: pretend that
the output is one character shorter.

> I can just see wrapping all of the printf-related functions into
> a single function with a few flags, and one of them being "include
> a trailing null" and that being that. A tiny up-front wrapper
> that passes false in on memprintf() and true on sprintf(). Seems
> so simple that it would've been worth the tiny effort by the C
> devs back in the day.

In terms of the additional manual-reading to figure out which
function to use, it's a huge cost.

Rick C. Hodgin

unread,
Jul 7, 2015, 6:15:03 PM7/7/15
to
Gordon Burditt wrote:
> [snip]

Wow.

Les Cargill

unread,
Jul 7, 2015, 10:48:27 PM7/7/15
to
Rick C. Hodgin wrote:
> Two questions:
>
> (1) Is there a version of sprintf() without a trailing NULL?
> (2) Why does sprintf() automatically insert a trailing NULL?
>
> TYIA!
>
> Best regards,
> Rick C. Hodgin
>

(1) No.
(2) Because the verb "sprintf" starts with "s" and this is 'C'.

--
Les Cargill

Phil Carmody

unread,
Jul 8, 2015, 1:10:00 PM7/8/15
to
That is an exceptionally elegant way to solve the problem.

Phil
--
A well regulated militia, being necessary to the security of a free state,
the right of the people to keep and bear arms, shall be well regulated.

Rick C. Hodgin

unread,
Jul 8, 2015, 1:11:58 PM7/8/15
to
On Wednesday, July 8, 2015 at 1:10:00 PM UTC-4, Phil Carmody wrote:
> Ben Bacarisse <ben.u...@bsb.me.uk> writes:
> > Ben Bacarisse <ben.u...@bsb.me.uk> writes:
> > <snip>
> > >>> >> Rick C. Hodgin <rick.c...@gmail.com> wrote:
> > >>> >> > (1) Is there a version of sprintf() without a trailing NULL?
> > <snip>
> > > sprintf already has the right mechanism -- the % syntax. You could
> > > propose a new conversion specifier which, like %%, denotes a single
> > > character -- a null. It would mainly be useful in your memprintf
> > > function:
> > >
> > > memprintf(buffer, "%4u%!", year); // terminated -- min 5 bytes written
> > > memprintf(buffer, "%4u%!", year); // min 4 bytes written, no null
> >
> > A thought occurs (it does happen)...
> > You could implement what you want as an extension. Since a trailing %
> > in the format is undefined behaviour, you could have a version of
> > sprintf that interprets such an invalid conversion specifier as a
> > request to omit the trailing null character.
>
> That is an exceptionally elegant way to solve the problem.

I couldn't agree more. I've spent several brief few-minute sessions
thinking about this since it was posted. It's beautiful. Almost as
if those original designers were looking to this future need. :-)

Phil Carmody

unread,
Jul 8, 2015, 1:20:17 PM7/8/15
to
Bartc <b...@freeuk.com> writes:
> (I sometimes use a string format that consists of a fixed field of N
> characters, is not zero-terminated, accommodates a string up to N
> characters, *and* it includes a count! But I don't expect a version of
> sprintf to directly write into such a buffer for me.)

You are using the word "string" ways which contradict the definition
of the term in the C standard.

string != array of char, nor pointer to the first element thereof

Phil Carmody

unread,
Jul 8, 2015, 1:24:16 PM7/8/15
to
Like you, I'm stranded.

Phil, avoiding the "a frayed knot" pun.

Rick C. Hodgin

unread,
Jul 8, 2015, 1:44:51 PM7/8/15
to
On Wednesday, July 8, 2015 at 1:24:16 PM UTC-4, Phil Carmody wrote:
> Joe Pfeiffer <pfei...@cs.nmsu.edu> writes:
> > "Rick C. Hodgin" <rick.c...@gmail.com> writes:
> > > Sjouke Burry wrote:
> > >> Rick C. Hodgin wrote:
> > >>> Two questions:
> > >>> (1) Is there a version of sprintf() without a trailing NULL?
> > >>> (2) Why does sprintf() automatically insert a trailing NULL?
> > >>> TYIA!
> > >>> Best regards,
> > >>> Rick C. Hodgin
> > >>
> > >> Look at the definition of a string. sprintf
> > >> produces a string, with all the trimmings
> > >> needed for a string. What is it you don't
> > >> understand about strings?
> > >
> > > Multi-threading.
> >
> > I've got *no* idea what your no-trailing-zero strings have to do with
> > multi-threading... explain, please?
>
> Like you, I'm stranded.
>
> Phil, avoiding the "a frayed knot" pun.

LOL!

Bartc

unread,
Jul 8, 2015, 1:51:18 PM7/8/15
to
On 08/07/2015 18:20, Phil Carmody wrote:
> Bartc <b...@freeuk.com> writes:
>> (I sometimes use a string format that consists of a fixed field of N
>> characters, is not zero-terminated, accommodates a string up to N
>> characters, *and* it includes a count! But I don't expect a version of
>> sprintf to directly write into such a buffer for me.)
>
> You are using the word "string" ways which contradict the definition
> of the term in the C standard.
>
> string != array of char, nor pointer to the first element thereof

I'm using 'string' in the wider sense of how it's used in computing.

The context made it clear that I didn't mean C's very narrow definition.
What should I have used? Any alternative would have made it harder to
understand.

--
Bartc

Bartc

unread,
Jul 8, 2015, 2:02:05 PM7/8/15
to
A couple of problems with it. First, someone has to implement it (update
the library routines for the printf family). (Or somehow write wrappers
around the existing printf routines, which might mean generating the
zero then getting rid of it, not exactly efficient.)

But a % at the of a string should be an error. If it now becomes an
acceptable format-specifier by itself, and /causes the trailing null to
be suppressed/, it can cause programs to crash if someone intended "%d"
but the "d" got missed out.

I think the idea to use %!, or any other accompanying code, is better.

--
Bartc

Richard Bos

unread,
Jul 9, 2015, 6:27:27 AM7/9/15
to
Phil Carmody <pc+u...@asdf.org> wrote:

> Bartc <b...@freeuk.com> writes:
> > (I sometimes use a string format that consists of a fixed field of N
> > characters, is not zero-terminated, accommodates a string up to N
> > characters, *and* it includes a count! But I don't expect a version of
> > sprintf to directly write into such a buffer for me.)
>
> You are using the word "string" ways which contradict the definition
> of the term in the C standard.

Which, ITYF if you read the rest of the thread, is the point of his
objection - and mine.

Richard

David Kleinecke

unread,
Jul 9, 2015, 12:02:44 PM7/9/15
to
Not that I much care about the % but I wonder what the best way
would really be. Wouldn't a warning when a trailing % was used be
enough? Like the warnings we get when '=' is used in a condiitonal.

I suppose the danger here is the "boy who called wolf" syndrome - if
we issue too many warnings none will be read. Personally I incline
to always expect the warnings to be read. But ...

Richard Heathfield

unread,
Jul 9, 2015, 12:07:51 PM7/9/15
to
On 09/07/15 17:02, David Kleinecke wrote:
> On Wednesday, July 8, 2015 at 11:02:05 AM UTC-7, Bart wrote:

<omitting the null terminating character from *printf output>

>> I think the idea to use %!, or any other accompanying code, is better.
>
> Not that I much care about the % but I wonder what the best way
> would really be.

The best way would be not to do it at all.

The *printf functions are generally used either for writing to a text
stream (in which case the null character isn't written anyway) or to a
string (in which case the null character is required, because otherwise
it isn't a string).

--
Richard Heathfield
Email: rjh at cpax dot org dot uk
"Usenet is a strange place" - dmr 29 July 1999
Sig line 4 vacant - apply within

glen herrmannsfeldt

unread,
Jul 9, 2015, 1:43:08 PM7/9/15
to
David Kleinecke <dklei...@gmail.com> wrote:

(snip, someone wrote)
>> I think the idea to use %!, or any other accompanying code, is better.

> Not that I much care about the % but I wonder what the best way
> would really be. Wouldn't a warning when a trailing % was used be
> enough? Like the warnings we get when '=' is used in a condiitonal.

By the way, %! reminds me, has anyone else noticed that HP printers
won't print files that start with %.

Postscript printers are supposed to recognize %! at the beginning,
but HP seems to have taken that down to just %. It then ignores
the rest of the file.

-- glen

Phil Carmody

unread,
Jul 10, 2015, 6:33:37 AM7/10/15
to
Bartc <b...@freeuk.com> writes:
> On 08/07/2015 18:11, Rick C. Hodgin wrote:
> > On Wednesday, July 8, 2015 at 1:10:00 PM UTC-4, Phil Carmody wrote:
> >> Ben Bacarisse <ben.u...@bsb.me.uk> writes:
>
> >>> You could implement what you want as an extension. Since a trailing %
> >>> in the format is undefined behaviour, you could have a version of
> >>> sprintf that interprets such an invalid conversion specifier as a
> >>> request to omit the trailing null character.
> >>
> >> That is an exceptionally elegant way to solve the problem.
> >
> > I couldn't agree more. I've spent several brief few-minute sessions
> > thinking about this since it was posted. It's beautiful. Almost as
> > if those original designers were looking to this future need. :-)
>
> A couple of problems with it. First, someone has to implement it
> (update the library routines for the printf family). (Or somehow write
> wrappers around the existing printf routines, which might mean
> generating the zero then getting rid of it, not exactly efficient.)
>
> But a % at the of a string should be an error.

Says who? If you, on what authority? If the C standard, chapter
and verse, please?

> If it now becomes an
> acceptable format-specifier by itself, and /causes the trailing null
> to be suppressed/, it can cause programs to crash if someone intended
> "%d" but the "d" got missed out.

Crashing was one of the available behaviours for such code anyway.
And crashing is usualy far better than silently propagating inappropriate
values around.

> I think the idea to use %!, or any other accompanying code, is better.

On this, your opinion has as much right to be voiced as anyone else's,
of course.

Bartc

unread,
Jul 10, 2015, 7:09:51 AM7/10/15
to
On 10/07/2015 11:33, Phil Carmody wrote:
> Bartc <b...@freeuk.com> writes:

>> But a % at the of a string should be an error.
>
> Says who? If you, on what authority? If the C standard, chapter
> and verse, please?

7.20.6.1p9 says the behaviour is undefined.

But whatever that says, under the current rules, a sole "%" at the end
of format string produces no output and serves no purpose.

In your opinion however, you would be quite happy to see that in code,
and would not question it? The possibility would never occur to you that
perhaps it's a mistake, and that a format code is missing or it's just
an extraneous character?

The problem is that under the new proposal, you can't distinguish such
an erroneous use from a legitimate one.

>> If it now becomes an
>> acceptable format-specifier by itself, and /causes the trailing null
>> to be suppressed/, it can cause programs to crash if someone intended
>> "%d" but the "d" got missed out.
>
> Crashing was one of the available behaviours for such code anyway.
> And crashing is usualy far better than silently propagating inappropriate
> values around.

Under the new proposed behaviour, any string created by sprintf would
not have terminating null if "%" was at the end of the format string.

This could give interesting results when subsequently processing by any
code or functions that expect it to be zero-terminated. And a sole
trailing "%" could occur inadvertently much more easily than with "%"
followed by a mandatory code.

But this, in your opinion, is perfectly acceptable!

--
bartc

Malcolm McLean

unread,
Jul 10, 2015, 7:51:34 AM7/10/15
to
On Friday, July 10, 2015 at 12:09:51 PM UTC+1, Bart wrote:
>
> This could give interesting results when subsequently processing by any
> code or functions that expect it to be zero-terminated. And a sole
> trailing "%" could occur inadvertently much more easily than with "%"
> followed by a mandatory code.
>
> But this, in your opinion, is perfectly acceptable!
>
That's a good point.

Someone just has to type

char *message = "sales up by %d%";

and he's in UB land, malicious buffer overflow attacks and all that.
But it's very easily done.

Phil Carmody

unread,
Jul 10, 2015, 10:07:16 AM7/10/15
to
Richard Heathfield <r...@cpax.org.uk> writes:
> On 09/07/15 17:02, David Kleinecke wrote:
> > On Wednesday, July 8, 2015 at 11:02:05 AM UTC-7, Bart wrote:
>
> <omitting the null terminating character from *printf output>
>
> >> I think the idea to use %!, or any other accompanying code, is better.
> >
> > Not that I much care about the % but I wonder what the best way
> > would really be.
>
> The best way would be not to do it at all.
>
> The *printf functions are generally used either for writing to a text
> stream (in which case the null character isn't written anyway) or to a
> string (in which case the null character is required, because
> otherwise it isn't a string).

Many of the str* functions do not require actual strings as their
parameters, merely (pointers to (first elements of)) arrays.
strncpy is the most obvious example, but I know that's a bit of
an embarassment to some as an outsider. Look for "array" in the
descriptions of the str* funcs.

But also look at the punchy 1-sentence summary of the behaviour
of sprintf before holding too firm to your assersions:
"""
The sprintf function is equivalent to fprintf, except that the output
is written into an array (speci#ed by the argument s) rather than to a
stream.
"""
fprintf outputs data, not strings. The destination is merely an array
not a "string" (as per the destination of strncpy). Mention of null
termination is secondary (the following sentence). One could implement
the trailing-% tweak without changing that initial sentence. OK, you'd
have to tweak the second one. but I believe in the inverted pyramid.

Keith Thompson

unread,
Jul 10, 2015, 11:33:26 AM7/10/15
to
Bartc <b...@freeuk.com> writes:
> On 10/07/2015 11:33, Phil Carmody wrote:
>> Bartc <b...@freeuk.com> writes:
>
>>> But a % at the of a string should be an error.
>>
>> Says who? If you, on what authority? If the C standard, chapter
>> and verse, please?
>
> 7.20.6.1p9 says the behaviour is undefined.

Yes -- but it's 7.21.6.1p6 in C11.

> But whatever that says, under the current rules, a sole "%" at the end
> of format string produces no output and serves no purpose.

What's your basis for saying it produces no output? The behavior is
undefined; it could print a '%' character, or the word "PERCENT", or it
could, in principle, do anything. (It happens to print nothing in the
implementation I'm using.)

--
Keith Thompson (The_Other_Keith) ks...@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"

Ben Bacarisse

unread,
Jul 10, 2015, 12:24:40 PM7/10/15
to
They are already in UB land -- nothing proposed alters that. What
alters is a statistical assessment of exactly what the UB is likely to
consist of, and how dangerous that might be, in the old and the new
case. I have no data to contribute to that assessment.

--
Ben.

Tim Rentsch

unread,
Jul 10, 2015, 10:03:09 PM7/10/15
to
Malcolm McLean <malcolm...@btinternet.com> writes:

> On Tuesday, July 7, 2015 at 12:11:09 PM UTC+1, Bart wrote:
>> On 07/07/2015 10:51, Malcolm McLean wrote:
>>
>>> C has to set out memory in that layout. If we further define
>>> that a string array is a set of string concatenated in memory,
>>> then you've got problems with the nul (but only if the length
>>> goes above 0xFFFFFF).
>>
>> There are a hundred languages that C could interface to, each
>> with its own string handling schemes. It would unreasonable to
>> expect C to accommodate all of them.
>
> C allows you to set up arbitrary binary structures in memory.
> [snip]

Correction: C allows you to set up arbitrary binary structures
in memory of the abstract machine. Nornally there is a one-to-one
mapping between memory in the abstract machine and memory in the
real machine, but there doesn't have to be.

Tim Rentsch

unread,
Jul 11, 2015, 7:59:02 AM7/11/15
to
Phil Carmody <pc+u...@asdf.org> writes:

> Ben Bacarisse <ben.u...@bsb.me.uk> writes:
>> Ben Bacarisse <ben.u...@bsb.me.uk> writes:
>> <snip>
>>>>>>>> (1) Is there a version of sprintf() without a trailing NULL?
>>
>> <snip>
>>
>>> sprintf already has the right mechanism -- the % syntax. You could
>>> propose a new conversion specifier which, like %%, denotes a single
>>> character -- a null. It would mainly be useful in your memprintf
>>> function:
>>>
>>> memprintf(buffer, "%4u%!", year); // terminated -- min 5 bytes written
>>> memprintf(buffer, "%4u%!", year); // min 4 bytes written, no null
>>
>> A thought occurs (it does happen)...
>> You could implement what you want as an extension. Since a trailing %
>> in the format is undefined behaviour, you could have a version of
>> sprintf that interprets such an invalid conversion specifier as a
>> request to omit the trailing null character.
>
> That is an exceptionally elegant way to solve the problem.

It's a clever idea, I'll grant you that. Not so sure it's a
good way to solve the problem, which IMO doesn't need solving
anyway.
0 new messages