Why uninitialized memory? [Summary (long)] 
Author Message
 Why uninitialized memory? [Summary (long)]

Many thanks to Mark C. Orton, Barry Margolin, Syed Zaeem Hosain, Tom
Watson and Jos Horsmeier. Mucho gracias for taking the time to
answer this query and solving the problem.

To those who suspected of it being another homework assignment [:)],
it is but a minute part of a silly attempt at a doct{*filter*}thesis.

The problem:
~~~~~~~~~~

If char s1[4] = "123";
   char s2[4] = "456";

then
   char s3[7];  such that s3 = "142536"

The quirk:
~~~~~~~~
When this[the code below] was attempted (where char *a, *b;)

 for (i = 0, a=s1, b=s2; i < 3; i++, a++, b++) {
   if (i == 0) {
     strncpy(s3, a, 1);
     strncat(s3, b, 1); <-------- Uninitialized Memory
   }
   else {
     strncat(s3, a, 1);
     strncat(s3, b, 1);
   }
 }

Purify complained about "uninitialized memory read"
(at the strncat() maked with an <-- above).

The solution(s):
~~~~~~~~~~~~~

________________________________

  The problem is that strncat() expects its first argument to be a
  NUL-terminated string.  However, strncpy() does not add the
  terminating NUL character, so strncat() searches past the portion of
  s3 that you've initialized (that is, the first character).  You are
  lucky in that the second character just happens to be NUL, so
  strncat() did what you wanted after all.  Perhaps Purify initialized
  s3 to NULs, but in general you can't count on this happening.

  I would suggest simply transferring the characters one at a time,
  s3[i++] = *a++; or something like that.  Don't try to shoehorn the
  string routines in, because this isn't what they're meant for.

________________________________

  The problem is that strncpy() only null-terminates the destination string
  if it stops due to encountering the null terminator of the source string.
  If it stops because they copied N characters, it doesn't write a null
  terminator.  Strncat() is reading uninitialized bytes when it looks
  for the null terminator to indicate where to append to.

  >From the man page for strncpy(3) (on SunOS 4.1.x):

                 If the length of s2 is less than n, strncpy() pads
       s1 with null characters.  If  the  length  of  s2  is  n  or
       greater,  s1  will  not  be null-terminated.

  You can solve this by initializing s3[] so that it contains all zeroes.
  Then, as characters are copied into it, it will always be null-terminated.

  >Why does this happen when strncat() is called the first time round
  >and not after (i.e. only at line 17)?.

  Strncat() always null-terminates the result.  So the error only happens
  after strncpy().

  >This is a test example that forms a part of a much larger problem
  >so I would like to have this cracked before proceeding. Any help
  >in the matter would be much appreciated. Thanx in advance.

  I hope the real program is copying more than one character at a time, as
  strncpy() and strncat() are silly ways to do that.  A much simpler way to
  code your above routine would be:

  int i;
  char *a, *b, *c;
  char s1[4] = "123";
  char s2[4] = "456";
  char s3[7];

  for (i = 0, a = s1, b = s2, c = s3; i < 3; i++) {
          *c++ = *a++;
          *c++ = *b++;
  }
  *c = 0; /* Add null terminator */

________________________________

  The fundamental problem is that your code assumes that "s3" is properly
  initialized to zeros (in all 7 bytes) before you start up - in fact,
  you just happen to luck out in your execution without memory leaks. I
  can demonstrate a problem, simply by initializing the array s3 to some
  sequence of chars - see later example.

  The strncpy you do (when i is 0) modifies the first byte of "s3". But
  the next strncat (second time through the loop) looks for the position
  of a terminating zero byte in "s3". Well, if this is the second byte in
  "s3", you luck out again!

  Clearly, if this happened to be a function on the stack, you would have
  found the problem very quickly - the stack may have had oddball values
  for the local variables, and the lack of initialization for "s3" would
  have jumped out at you!

  In any case, to get a feel for what is happening, I suggest you look
  and see what adding an initial value for "s3" does, as in the code I
  show here.

  Here, I have deliberately set all the bytes in s3 to the letter 'z' for
  randomness (including a properly terminating null byte). Now see what
  happens:

  zodiac{660}szh: cat foo.c
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>

  main()
  {
      int i;
      char *a, *b;
      char s1[4] = "123";
      char s2[4] = "456";
      char s3[7] = "zzzzzz";

      for (i = 0, a=s1, b=s2; i < 3; i++, a++, b++) {
          if (i == 0) {
              strncpy(s3, a, 1);
              strncat(s3, b, 1);
          }
          else {
              strncat(s3, a, 1);
              strncat(s3, b, 1);
          }
      }

      printf("%s\n", s3);
  }
  zodiac{661}szh: gcc -o foo foo.c
  zodiac{662}szh: foo
  1zzzzz4253
  zodiac{663}szh:

  Which shows incorrect output from the program simply by improper initial
  values for the bytes in "s3"!

  *PLUS* you forget the terminating zero byte for "s3" in your code.

  One other minor point. The strncat for "b" can be moved outside the
  "if" for simplicity. A good compiler will probably do this anyway, but
  ... why not help it a little with clearer code?

  Here is how to do the above a little bit more cleanly (still not
  totally optimized or perfect, but I am choosing to show how to use
  pointers here for greater clarity - I am sure that you can simplify
  this further once you understand how it works):

  zodiac{667}szh: cat foo.c
  #include <stdio.h>
  #include <stdlib.h>

  int
  main()
  {
      int i;
      char *a , *b , *c;
      char s1[4] = "123";
      char s2[4] = "456";
      char s3[7] = "zzzzzz";

      for (i = 0, a=s1, b=s2, c=s3; i < 3; i++, a++, b++) {
          *c++ = *a;
          *c++ = *b;
      }
      *c = '\0';

      printf("%s\n", s3);
      return (0);
  }
  zodiac{668}szh: gcc -o foo foo.c
  zodiac{669}szh: foo
  142536
  zodiac{670}szh:

  Note that the initial value (initialized or not) for "s3" is no longer
  relevant. I overwrite the bytes as I need with pointers.

  The one other point I will make is that using function calls to strncat
  for single bytes is not particularly efficient or effective - might as
  well write simple pointer code yourself, as I show above.

________________________________

  Well, a couple of things.  1)  If you are copying a character at a time,
  just doing s3[n] = s1[i] is probably easier.  2)  Your calls to strncat
  only copy ONE character, and not the null terminator.  You will need
  to add it in later.

  See below...

  >   for (i = 0, a=s1, b=s2; i < 3; i++, a++, b++) {
  >     if (i == 0) {
  Here s3[0] thru s3[6] (7 items) are all un initialized, which is OK.
  >       strncpy(s3, a, 1);
  Now the strncpy copies only 1 character from the source b (what the pointer
  points to) to the result string.  This gets us s3[0] = '1'.  s3[1] thru
  s3[6] are still undefined.
  >       strncat(s3, b, 1);
  Now we want to add to the s3 string.  To do that the routine 'strncat' must
  search for the end of the string.  It looks at s[0], finds '1' and
  goes to s[1] where it finds uninitialized memory, and thus your 'trap'.

  a better way to do this would be to just copy the characters, and NOT use
  the string functions provided by the library.  So:
  ----cut here----

  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>

  main ()
  {
    int i
    char *a;
    char s1[] = "123";
    char s2[] = "456";
    char s3[sizeof(s1) + sizeof(s2) - 1];    /* Size of both strings, less
  one
                                                of the terminating nulls */

    for (i = 0, a = s3; i < strlen(s1); i++)
      {
      *s3++ = s1[i];
      *s3++ = s2[i];
      }
    *s3 = '\0';                              /* Terminate the string */

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

  ----on the dotted line----

  The simplifing assumption is that both of the composite strings are of the
  same length, and you desire them to be interleaved.  I have NOT tried out
  this code on a compiler, so it may have errors, but it is probably close.

________________________________

  The strncat function expects a '\0' terminated string as its first
  argument. The automatic variable s3 is not explicitely initialized,
  and therefore it contains garbage values. More than likely, it
  doesn't contain an (empty) '\0' terminated string. That's why you've
  got Purify complaining about that particular line.

  But I wanted to say something else ... why are you using those
  string manipulation functions, just for copying one character
  at the time? You don't need any functions to do just that. Have
  a look at the following snippet of code and tell me whether or
  not you like it better than your solution:

  void merge(char* s1, char *s2, char *s3) {

  while (*s1) {                   /* assume strlen(s1) == strlen(s2) */
          s3[0]= s1[0]; s1++;     /* catenate one char from s1 */
          s3[1]= s2[0]; s2++;     /* catenate one char from s1 */
          s3+= 2;                 /* s3 points to next two positions */
  }

  s3[0]= '\0';                    /* terminate the string */

  Sprinkling some C-lingo into the while loop yields:

  while (*s1) {                   /* assume strlen(s1) == strlen(s2) */
          *s3++= *s1++;           /* catenate one char from s1 */
          *s3++= *s2++;           /* catenate one char from s2 */
  }

  And after applying some real trickery-dickery (I love doing that ;-)

  while ((*s3++= *s1++) && (*s3++= *s2++))
          ;

----

snailM: Dept. of Marine Technology     |        home :  (+44 91) 213 0903
...

read more »



Tue, 27 Aug 1996 15:59:18 GMT  
 Why uninitialized memory? [Summary (long)]


[with some stuff deleted for brevity]

Quote:
>To those who suspected of it being another homework assignment [:)],
>it is but a minute part of a silly attempt at a doct{*filter*}thesis.

BTW, the reason you got a long response from me (with an attempt to
help ... I did *not* think it was a homework assignment) was because
you took the time to show code that you had actually tried. And clearly
demonstrating the error and the difficulty you were having.

Even if had been a homework problem, I would have helped, because you
demonstrated that you were working on it yourself, and simply needed
guidance to proceed correctly. More people ought to follow your
example!    :-)

                                                                Z

--
-------------------------------------------------------------------------
| Syed Zaeem Hosain          P. O. Box 610097            (408) 441-7021 |

-------------------------------------------------------------------------



Fri, 30 Aug 1996 15:18:08 GMT  
 
 [ 2 post ] 

 Relevant Pages 

1. Why uninitialized memory?

2. char size (was long long long long long int)

3. Death by error checks. (Summary - Long)

4. long random numbers / MS C (Summary)

5. Summary of ShareWare experiences (Moderately long).

6. "Freelance" summary (long)

7. Makefile summary (long)

8. efficiency of switch (summary of replies: LONG)

9. Summary: C Puzzle: Not Easy! [long]

10. SUMMARY: Request for BigNum Packages (LONG)

11. Summary of C tools survey (LONG)

12. long long long long integers

 

 
Powered by phpBB® Forum Software