Weird sprintf behavior

X

Xcriber51

Hi

The idiom of updating a value by adding something to it is so common that
some programming languages support it at the level of strings, such as;

s = "_";
s = s + "c"; // s == "_c"
s = s + "o"; // s == "_co"

etc.

C, being more "honest" (or, if you're unable to appreciate that, more
"cumbersome") on strings, doesn't provide such a facility. So, I'm trying
to emulate it by using the "sprintf" standard library function. (Original
idea, I know.) But it isn't behaving as expected.

Consider the following routine:

void _concat()
{
char *s;

s = "_";

sprintf(s,"%s%c",s,'c');
sprintf(s,"%s%c",s,'o');
sprintf(s,"%s%c",s,'n');
sprintf(s,"%s%c",s,'c');
sprintf(s,"%s%c",s,'a');
sprintf(s,"%s%c",s,'t');

printf("%s\n",s);

s = "t";

sprintf(s,"%c%s",'a',s);
sprintf(s,"%c%s",'c',s);
sprintf(s,"%c%s",'n',s);
sprintf(s,"%c%s",'o',s);
sprintf(s,"%c%s",'c',s);
sprintf(s,"%c%s",'_',s);

printf("%s\n",s);
}

Here, the first round of calls prints "_concat", as expected. The second,
however, outputs "_______".

Why?

Please keep it short, and I'm not interested in suggestions for
alternatives. Just a technical reply on why switching the place of the
format specifiers for character and string yields this asymmetrical
result.

Thanks!


-- K
 
G

gw7rib

void _concat()
{
char *s;

s = "_";

sprintf(s,"%s%c",s,'c');
sprintf(s,"%s%c",s,'o');
sprintf(s,"%s%c",s,'n');
sprintf(s,"%s%c",s,'c');
sprintf(s,"%s%c",s,'a');
sprintf(s,"%s%c",s,'t');

printf("%s\n",s);

s = "t";

sprintf(s,"%c%s",'a',s);
sprintf(s,"%c%s",'c',s);
sprintf(s,"%c%s",'n',s);
sprintf(s,"%c%s",'o',s);
sprintf(s,"%c%s",'c',s);
sprintf(s,"%c%s",'_',s);

printf("%s\n",s);

}

A couple of problems here. Firstly, if you're going to create a string
you need somewhere to put it. Here, "s" only has space for one
character. And writing to string constants may go wrong. However,
neither of these seems to have gone wrong for you yet...

The second problem is that you are modifying the string while you read
it, and this seems to be why the second version is going wrong. If s
contains, for example, "concat", and you modify it by putting a "_" at
the beginning, followed by the first letter of s, followed by the
second letter of s, etc etc, then the result will be "_" followed by
the first character of s (was a "c" but we've just turned it into a
"_") followed by the second character of s (was an "o" but we've just
turned that one into a "_" too) followed by ... well, you get the
idea.
Please keep it short, and I'm not interested in

strcat

Hope that helps.
Paul.
 
R

Richard Heathfield

Xcriber51 said:

sprintf(s,"%c%s",'_',s);

printf("%s\n",s);
}

Here, the first round of calls prints "_concat", as expected. The second,
however, outputs "_______".

Why?

Please keep it short,

7.19.6.6(2)
 
J

Johannes Bauer

Richard said:
7.19.6.6(2)

Great answer ;-)

You don't happen to have a link where the whole C99-standard can be
found? I googled, but found nothing. Maybe the ANSI wants money for that?

Greetings,
Johannes
 
R

Richard Heathfield

Johannes Bauer said:
Great answer ;-)

You don't happen to have a link where the whole C99-standard can be
found? I googled, but found nothing. Maybe the ANSI wants money for that?

For the "Real Thing" so-called, yes, they want your plastic.

But the latest full discussion draft, n1256.pdf IIRC, will almost certainly
serve your needs (provided you don't get into a fight with Chris Hills),
and can be downloaded for free from
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf
 
C

Chris Dollin

Xcriber51 said:
Thanks, both. No more replies needed.

I expect you'll get some, though, propagation delays &co
being what they are, and people wanting to have their
say.
 
K

Keith Thompson

Johannes Bauer said:
Great answer ;-)

You don't happen to have a link where the whole C99-standard can be
found? I googled, but found nothing. Maybe the ANSI wants money for that?

Yes, ANSI (or your national standard body, if you're not in the US)
wants money for the actual standard, which is currently C99. You can
get a copy of C99 for about $18 (or did it go up?), or a paper copy
for a couple hundred or so.

Personally, I find the PDF standard to be well worth the money.

But the latest draft, which incorporates the C99 standard plus three
Technical Corrigenda, is a free download at
<http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf>. I
actually use n1256 rather than the official standard most of the time.
All changes relative to the standard are marked with change bars.

The Technical Corrigenda themselves are also available. I think ANSI
has the first two for free, and I *think* the committee site
(.../wg14/...) has all three. These are short documents with just the
changes from C99. In combination with the standard itself, they're
probably more "offical" than n1256, but considerably more unwieldy.
 
?

=?iso-2022-kr?q?=1B=24=29CHarald_van_D=0E=29=26=0F

Yes, ANSI (or your national standard body, if you're not in the US)
wants money for the actual standard, which is currently C99. You can
get a copy of C99 for about $18 (or did it go up?),

It did, but at $30 it's still quite a bit cheaper than the alternatives.
 
C

CBFalconer

Xcriber51 said:
.... snip ...

C, being more "honest" (or, if you're unable to appreciate that,
more "cumbersome") on strings, doesn't provide such a facility.
So, I'm trying to emulate it by using the "sprintf" standard
library function. (Original idea, I know.) But it isn't behaving
as expected.

Consider the following routine:

void _concat()
{
char *s;

s = "_";

s has been set to point to a constant string. That same constant
string may be used other places. Therefore it is NOT writable.
Any attempt to write to it results in undefined behaviour. You
could use:

void concat() { /* use of leading _ is forbidden */

static char s[MAX];

strcpy(s, "_"); /* to make it writeable */
 
K

Keith Thompson

CBFalconer said:
You can get the text version of N869, the last draft before C99 was
published, slightly reorganized to ease quoting and searching, and
bzip2 compressed, at:

<http://cbfalconer.home.att.net/download/n869_txt.bz2>

It has the disadvantage that (a) some of the formatting is lost (in
particular, terms are shown in italics when they're defined, and the
plain text version doesn't reflect this), and (b) it's a pre-standard
draft. The differences betwen n869 and the approved C99 standard
aren't huge, but there are some.

Personally, I find the PDF version at least as useful as the
plain-text version. If you can deal with PDF, I urge you to use
n1256.pdf, or the actual standard, rather than n869. (This is
addressed to Johannes Bauer, and/or to anyone else who's reading; I
don't expect Chuck to change his mind on this point.)
 
X

Xcriber51

Thanks. You're right, but that was done in a hurry just to throw a simple
illustration of the problem to this forum, and the leading underscore was
just to avoid a clash with another concat in that file.

But anyway, enough about ourselves... :)

Here's a challenge to all my fellow hackers, then. If I modify that
illustration as follows...

void concat()
{
char *s, *buff;
// 8 is arbitrary, nevermind that
s = (char *) malloc(sizeof(char) * 8);
buff = (char *) malloc(sizeof(char) * 8);

strcpy(buff,"t");

sprintf(s,"%c%s",'a',buff); strcpy(buff,s);
sprintf(s,"%c%s",'c',buff); strcpy(buff,s);
sprintf(s,"%c%s",'n',buff); strcpy(buff,s);
sprintf(s,"%c%s",'o',buff); strcpy(buff,s);
sprintf(s,"%c%s",'c',buff); strcpy(buff,s);
sprintf(s,"%c%s",'_',buff); strcpy(buff,s);

printf("%s\n",s);

free(s);
free(buff);
}

...this time, simply because I first copy the sprintf'ed string to a
buffer everytime, I avoid that problem, and the output is expected.

I know that a "strcat" exists, but that only concats two strings, not an
arbitrary (variadic) number of parameters of various types into a string.
sprintf is so convenient, you can write an "itoa" function in seconds like
so (don't obsess with efficiency here):

char *itoa(int n)
{
static char buff[15];
sprintf(buff,"%d",n);

return buff;
}

So I [naively, I realize] thought I could have that convenience any way I
like. (I later checked an implementation of the function and found this:
http://tinyurl.com/2r7c27. Apparently, you need a PhD to implement an
sprintf function :D)

Here's the challenge:

Write a "catsprintf" function that avoids this shortcoming -- i.e. even if
one of the string parameters is the target string itself, you gently copy
that to a buffer, and then print everything (including that buffer) into
the target string.

NOTE: Try to write a version not from the ground up but utilizing the
standard sprintf. I want to see how you deal with the variadic parameter
list -- especially when one of the parameters is the destination string
how you avoid overwriting it.

(NB: if this is a pointless exercise for you, please avert your eyes and
avoid adding noise and negativity to the thread.)

I'm a 40-something, so I'm not asking for you to do my homework or
anything.

Thanks

-- K
 
B

Ben Bacarisse

Xcriber51 said:
Here's the challenge:

Write a "catsprintf" function that avoids this shortcoming -- i.e. even if
one of the string parameters is the target string itself, you gently copy
that to a buffer, and then print everything (including that buffer) into
the target string.

A couple of points. The name is a bit odd since it does not obviously
"cat" anything ("cat" as in concatenate). It is better to work with
the new size-limited versions snprintf and vsnprintf since these can
ensure the target is not over-run. Also, this size-limited way of
thinking leads to a simpler solution:

#include <stdarg.h>
#include <stdio.h>

int catsnprintf(char *s, size_t n, const char *format, ...)
{
char t_string[n];
va_list alist;
va_start(alist, format);
int n_req = vsnprintf(t_string, n, format, alist);
va_end(alist);
memcpy(s, t_string, n_req > n ? n : n_req);
return n_req;
}

To avoid using VLAs, a malloc/free pair is required.
 
X

Xcriber51

Thanks, Ben. So it's that simple, and yet I thought of it as a minor
challenge. (That implementation of the original must have blurred my
vision.)

What was I thinking?


-- K
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,819
Latest member
masterdaster

Latest Threads

Top