[prog] [C] strings bug

Meredydd Luff meredydd at everybuddy.com
Thu Apr 10 09:50:51 EST 2003


On Wednesday 09 April 2003 22:55, Conor Daly wrote:
> /* This is main.c */
> #include<stdio.h>
> #include<string.h>
> #include<stdarg.h>
>
> int main(void);
> int printout(void);
> char *strdup2(const char *, const char *);
> char *strset(char *, const char *);
> char *stradd(char *, const char *);
>
> 	char *string1;
> 	char *string2;
> 	char *string3;
Should these be local to main()? Oh, I see, you're using globals to pass the 
values to printout(). That's ugly and shouldn't be used outside this little 
testing hack, but then I'm pretty sure you already knew that :)

>
> int main(void)
> {
>
> 	printout();
When this function is called, the values of string1, string2, and string3 are 
undefined - they could technically be anything. Looking at your output, 
you're in luck. They seem to have been set to NULL, but don't rely on this 
happening. These three pointers could be pointing anywhere right now - NULL, 
somewhere in the middle of your program's memory space, somewhere in some 
other program's memory space, somewhere in the middle of nowhere... Of 
course, accesses to either of those last two will be caught and result in a 
SIGSEGV. It's clobbering your own data you have to be wary of.

> 	string1 = strset(string1, "Test");/**/
What's the desired result of this? Looking at your implementation of strset() 
a little further down, it looks like what you're doing could just be 
accomplished by:

char * strset(char * dest, char * src)
{
  if(dest!=NULL) { free(dest); }

  return strdup(src);
}

Correct me if I'm wrong, but the error condition in which malloc() (which is 
called by strdup()) returns NULL only occurs when the machine has run out of 
both memory and swap space, by which point you're so hosed that one little 
program segfaulting isn't going to make that much of a difference (by that 
point you can't even halt the machine, because there's no memory to spawn the 
"shutdown" process). In any case, the program should "fail fast" on that type 
of error - an immediate bail-out is much easier to debug than subtle bugs 
caused by the old contents of a string sticking around past an assignment, 
and even if you want to bail out gracefully rather than with a SIGSEGV, 
checking whether a pointer is NULL is much faster and easier than a strcmp() 
to check whether it has changed.


> 	string3 = strset(string3, "Addition");
> 	printout();
You still haven't set string2 yet - again, this time it was conventiently 
NULL, but don't bank on it.

Despite all of the above, the lucky coincidence of them all being initially 
NULL has meant that both assignments so far have worked out fine.

> 	string2 = strset(string2, "Test");/**/
> 	string1 = strset(string1, "Testing");/**/
> 	printout();
>From the results of printout() here, it looks like something awful has 
happened to string3 over the course of those two strset() calls. This is a 
bug in strset(), but I'll get onto that later. Using the function I knocked 
up above, it works fine.

> 	string1 = stradd(string1, ", Testing...");/**/
> 	string3 = strdup2(string3, "Addition");
> 	printout();
Again, with those minor modifications to strset(), this works fine too.

> 	printf("%s, %s\n",  strset(string3, "Direct print"),  strset(string3, " on
> screen")); }
Now here we get problems. My implementation of strset() segfaults here, and 
with good reason. The point of strset(), is it not, is to free "dest" if 
necessary, and then strdup() src and return the new pointer. The way that it 
tells whether freeing src is necessary is whether or not it is NULL. This 
works fine, as long as you always assign the return value of strset() to 
whatever you passed as its first argument ("dest"). Here, however, you don't 
do that, so you free() the are of memory pointed to by string3 twice. 
Normally (always?), this results in a segfault.

Now, we could do this:
  printf("%s, %s\n",  (string3=strset(string3, "Direct print")),
	(string3=strset(string3, "on screen")) );
Unfortunately, this won't work either. The reason is that when the C compiler 
compiles things, it unrolls that line into something like this:

char * p1, * p2, * p3;

p1="%s, %s\n";
p2=(string3=strset(string3, "Direct print"));
p3=(string3=strset(string3, "on screen"));

printf(p1, p2, p3);

Do you see the problem? The bit of memory pointed to by p2 (and, at the time 
of assignment, string3 too) is free()ed by the line that assigns p3. By the 
time printf() gets called, it's being passed a pointer to unallocated memory.

OK, just a quick comment on the problem with this strset():

> /* *************strset()**************************
>  * Uses strdup() to set an existing string
>  */
> char *strset(char *dest, const char *src) {
>
> 	/* create a tmp string to hold 'dest' while we free that */
> 	char *tmp;
Again, tmp isn't assigned, and could be anything.

>
> 	printf("in function strset()\n");
> 	if(dest)
> 	{
>
> 	/* take a copy of dest */
> 	if( (tmp = strdup(dest)) == NULL)
> 		{
> 			return(dest); /* we failed to make a copy so bail
> 					out */
> 		}
> 	/* then free it */
> 	free(dest);
> 	}
OK, that's all well and good, but what happens if dest==NULL? tmp doesn't get 
touched - but below, it gets free()d, whatever it points to! In the test run 
you gave, I suspect that by pure coincidence, it ended up pointing to the bit 
of memory that string3 was also pointing at, so when that free() call 
happened, it freed the third string, which promptly got clobbered by 
something else.

Right, I hope that helps. Do yell at me if it didn't :^)

Meredydd
-- 

Everybuddy project maintainer
http://www.everybuddy.com/

MSN:     blip109 at hotmail.com
AIM:     blip109
Yahoo:   modula7


More information about the Programming mailing list