CS360 Lecture notes -- Pointers, String Routines, Malloc

  • James S. Plank
  • Directory: /home/plank/cs360/notes/PointMalloc
  • Lecture notes: http://www.cs.utk.edu/~plank/plank/classes/cs140/notes/PointMalloc/
  • Lecture notes directory: /home/plank/cs140/notes/PointMalloc
  • Fri Aug 31 10:39:16 EDT 2007.
  • Last modified: Thu Jan 9 12:18:15 EST 2014
    There is quite a bit of repetition between this lecture and the last one. That is intentional, because repetition helps you learn. So even though you've seen very similar stuff, it's good for you to go over these lecture notes along with the previous lecture notes.
    We've used pointers in CS140 and CS302. If you want some review, please see pointer lecture notes from CS140. For additional reinforcement, I have a set of old lecture notes from CS302 where I set up more STL data structures that point to each other with pointers. Both of these are in C++.

    There is no standard template library in C. This means that vectors, lists, sets and maps are gone. We will replace all of them in the next few lectures. We'll start with vectors. In C, we use arrays instead of vectors. You can statically declare an array by putting [size] in the variable declaration. For example, the following variable declaration will create an array iarray of ten integers:

    int iarray[10];
    

    You can access the elements of iarray in square brackets. Unlike C++, iarray has no methods. In particular, the size of iarray is not stored anywhere -- you have to keep track of it yourself.

    In reality, iarray is a pointer to the first element of the array. In other words, there are 40 bytes allocated for the array (since integers are four bytes each), and iarray points to the first of these. If we want, we can set a second pointer to iarray, and we can print the elements of iarray by incrementing the pointer and dereferencing it. We do all of that in ptr1.c:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    {
      int iarray[10];
      int *ip;
      int i;
    
      for (i = 0; i < 10; i++) iarray[i] = 100+i;
    
      printf("iarray = 0x%lx\n", (unsigned long) iarray);
    
      ip = iarray;
    
      for (i = 0; i < 10; i++) {
        printf("i=%d.  iarray[i]=%d.  ip = 0x%lx.  *ip=%d.  (ip-iarray)=%d\n", 
            i, iarray[i], (unsigned long) ip, *ip, (int) (ip-iarray));
        ip++;
      }
    }
    

    Let me go over some of the more subtle things about this program. First, we print pointers with "0x%lx" in the printf formatting string. This prints it in hexadecimal, and treats it as a long. If you're unfamiliar with a long, this is an integer data type, which is 4 bytes long on a 32-bit architecture, and 8 bytes long on a 64-bit architecture. Most machines these days are 64-bit, so longs are usually 8 bytes. Pointers, like longs, are variable in size, depending on the architecture. Their size will be equal to the size of a long. This is why, when you print out a pointer, you treat it like a long.

    The gcc compiler has become really anal over the years. That's not necessarily a bad thing, but it can be irritating. One bit of anal-ness that gets on my nerves is that when you put "%lx" in a formatting string, it expects an unsigned long. That is why I have to typecast the pointer in the two printf statements.

    In the for loop, we print out five quantities:

    Let's take a look at output:
    UNIX> ptr1
    iarray = 0x7fff5fbfdc40
    i=0.  iarray[i]=100.  ip = 0x7fff5fbfdc40.  *ip=100.  (ip-iarray)=0
    i=1.  iarray[i]=101.  ip = 0x7fff5fbfdc44.  *ip=101.  (ip-iarray)=1
    i=2.  iarray[i]=102.  ip = 0x7fff5fbfdc48.  *ip=102.  (ip-iarray)=2
    i=3.  iarray[i]=103.  ip = 0x7fff5fbfdc4c.  *ip=103.  (ip-iarray)=3
    i=4.  iarray[i]=104.  ip = 0x7fff5fbfdc50.  *ip=104.  (ip-iarray)=4
    i=5.  iarray[i]=105.  ip = 0x7fff5fbfdc54.  *ip=105.  (ip-iarray)=5
    i=6.  iarray[i]=106.  ip = 0x7fff5fbfdc58.  *ip=106.  (ip-iarray)=6
    i=7.  iarray[i]=107.  ip = 0x7fff5fbfdc5c.  *ip=107.  (ip-iarray)=7
    i=8.  iarray[i]=108.  ip = 0x7fff5fbfdc60.  *ip=108.  (ip-iarray)=8
    i=9.  iarray[i]=109.  ip = 0x7fff5fbfdc64.  *ip=109.  (ip-iarray)=9
    UNIX> 
    
    Everything in hex will change from machine to machine. However, their interrelationship will always be the same. In the for loop, i, iarray[i] and *ip should all be straightforward and require no explanation. I'll explain the others in detail.

    First, although pointers seem like giant, weird hex numbers, you need to think of them as indices into memory. When your program starts running, the operating system sets up memory for you. Memory is a giant array of bytes that goes from 0 to 0xffffffff on a 32-bit machine, and from 0 to 0xffffffffffffffff on a 64-bit machine. The subtle thing about this array is that not all indices are valid. This is for good reason -- your computer does not have 264 bytes of memory, and even if it did, it would be wasteful to give all of that memory to every program.

    If you try to access parts of memory that are not valid, you will get a "segmentation violation." Typically, address 0 is not valid for just this reason -- when you have an uninitialized pointer and its value is zero, then you get a segmentation violation when you try to access it. This helps you debug.

    When this program is running, the operating system has set it up so that the 40 bytes starting with 0x7fff5fbfdc40 are where iarray is stored. That is why iarray is equal to 0x7fff5fbfdc40. If iarray[0] is the four bytes that start at 0x7fff5fbfdc40, then iarray[1] must be the four bytes that start at 0x7fff5fbfdc44. This is why ip is equal to 0x7fff5fbfdc44 on the second iteration of the for loop.

    While this is all logical, it is a little confusing: adding one to ip actually adds four to the value. This is called "pointer arithmetic" -- when you add x to a pointer, it really adds sx to it, where s is the size of data to which the pointer points.

    The last value printed in the for loops is a little confusing too. Again, focus on the line where i equals one. ip is equal to 0x7fff5fbfdc44, so you would think that (ip-iarray) would equal four. It does not, because the compiler is doing pointer arithmetic -- from the point of view of the compiler, when you say "ip-iarray," you are asking for the number of elements between ip and iarray. That will be the difference between the pointers, divided by the size of the element. In this case, it is (0x7fff5fbfdc44-0x7fff5fbfdc40)/4, which equals one.

    To help hammer this home a little further, I have three other programs where are pretty much identical to ptr.c:

    That last one is worth looking at it its entirety:

    #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct {
      double d1;
      double d2;
    } Two;
    
    int main()
    {
      Two iarray[10];
      Two *ip;
      int i;
    
      for (i = 0; i < 10; i++) { 
        iarray[i].d1 = 100+i;
        iarray[i].d2 = 200+i;
      }
    
      printf("iarray = 0x%lx\n", (unsigned long) iarray);
    
      ip = iarray;
    
      for (i = 0; i < 10; i++) {
        printf("i=%d.  iarray[i]={%.2lf,%.2lf}.  ip = 0x%lx.  *ip={%.2lf,%.2lf}.  (ip-iarray)=%d\n", 
            i, iarray[i].d1, iarray[i].d2, (unsigned long) ip, ip->d1, ip->d2, (int) (ip-iarray));
        ip++;
      }
    }
    

    In particular, I use the arrow to dereference different parts of the struct from the pointer: ip->d1 accesses d1, and ip->d2 accesses d2.

    You can force your machine into 32-bit mode by using the compiler directive -m32. Let's do that with the last program:

    UNIX> gcc -m32 -o sptr sptr.c
    UNIX> sptr
    iarray = 0xbfffdd98
    i=0.  iarray[i]={100.00,200.00}.  ip = 0xbfffdd98.  *ip={100.00,200.00}.  (ip-iarray)=0
    i=1.  iarray[i]={101.00,201.00}.  ip = 0xbfffdda8.  *ip={101.00,201.00}.  (ip-iarray)=1
    i=2.  iarray[i]={102.00,202.00}.  ip = 0xbfffddb8.  *ip={102.00,202.00}.  (ip-iarray)=2
    i=3.  iarray[i]={103.00,203.00}.  ip = 0xbfffddc8.  *ip={103.00,203.00}.  (ip-iarray)=3
    i=4.  iarray[i]={104.00,204.00}.  ip = 0xbfffddd8.  *ip={104.00,204.00}.  (ip-iarray)=4
    i=5.  iarray[i]={105.00,205.00}.  ip = 0xbfffdde8.  *ip={105.00,205.00}.  (ip-iarray)=5
    i=6.  iarray[i]={106.00,206.00}.  ip = 0xbfffddf8.  *ip={106.00,206.00}.  (ip-iarray)=6
    i=7.  iarray[i]={107.00,207.00}.  ip = 0xbfffde08.  *ip={107.00,207.00}.  (ip-iarray)=7
    i=8.  iarray[i]={108.00,208.00}.  ip = 0xbfffde18.  *ip={108.00,208.00}.  (ip-iarray)=8
    i=9.  iarray[i]={109.00,209.00}.  ip = 0xbfffde28.  *ip={109.00,209.00}.  (ip-iarray)=9
    UNIX> 
    
    As you can see, the pointers are smaller (8 hex digits). However, their interrelationship is the same, and each time you increment ip, its value is increased by 16.

    Little Quiz

    More pointers: Behold the following C program (in quiz.c):

    /* Line 1 */   #include <stdio.h>
    /* Line 2 */  
    /* Line 3 */   int main()
    /* Line 4 */   {
    /* Line 5 */     int i, array[10];
    /* Line 6 */     int *ip, *a1;
    /* Line 7 */     int **ipp;
    /* Line 8 */   
    /* Line 9 */     ip = &i;
    /* Line 10 */    ipp = &ip;
    /* Line 11 */    a1 = &(array[1]);
    /* Line 12 */  
    /* Line 13 */    for (i = 0; i < 10; i++) array[i] = i;
    /* Line 14 */  
    /* Line 15 */    i = 11;
    /* Line 16 */  
    /* Line 17 */    printf("ip: 0x%lx, &ip: 0x%lx, array: 0x%lx\n", (unsigned long) ip, (unsigned long) &ip, (unsigned long) array);
    /* Line 18 */    printf("\n");
    /* Line 19 */    
    /* Line 20 */  
    /* Line 21 */    printf("&i: 0x%lx\n", (unsigned long) &i);
    /* Line 22 */    printf("ipp: 0x%lx, *ipp: 0x%lx, **ipp: 0x%lx\n", (unsigned long) ipp, (unsigned long) *ipp, (unsigned long) **ipp);
    /* Line 23 */    printf("\n");
    /* Line 24 */    printf("a1: 0x%lx, *a1: 0x%lx\n", (unsigned long) a1, (unsigned long) *a1);
    /* Line 25 */  
    /* Line 26 */    a1 += 4;
    /* Line 27 */    *a1 = 500;
    /* Line 28 */    
    /* Line 29 */    for (i = 0; i < 10; i++) {
    /* Line 30 */      printf("%d ", array[i]);
    /* Line 31 */    }
    /* Line 32 */    printf("\n");
    /* Line 33 */  }
    

    When you run this, the first line of output is:

    UNIX> quiz
    ip: 0xeffff9fc, &ip: 0xeffff9cc, array: 0xeffff9d0
    
    
    What is the rest of the output?

    (In class, I used the following drawing to help illustrate. You can get them in Little-Quiz-Helper.odp (Open Office) or Little-Quiz-Helper.pdf (PDF).)

    This is tricky, but you should be able to do it with all you currently know about pointers. This is the kind of question I am fond of asking on tests. Here's the answer. If you want to make sure you're doing things right, try to draw a picture of memory and fill in what that first line tells you. Here would be my picture. We'll start with a blank slate with the relevant addresses from the first line of the program:

    0xeffff9cc: |                        |
    0xeffff9d0: |                        |
    0xeffff9d4: |                        |
    0xeffff9d8: |                        |
    0xeffff9dc: |                        |
    0xeffff9e0: |                        |
    0xeffff9e4: |                        |
    0xeffff9e8: |                        |
    0xeffff9ec: |                        |
    0xeffff9f0: |                        |
    0xeffff9f4: |                        |
    0xeffff9f8: |                        |
    0xeffff9fc: |                        |
    

    Now, what do we know from the first line of output. Well, the address of ip is 0xeffff9cc, and its value is 0xeffff9fc. So we can draw in its value at that address:

    0xeffff9cc: | ip = 0xeffff9fc        |
    0xeffff9d0: |                        |
    0xeffff9d4: |                        |
    0xeffff9d8: |                        |
    0xeffff9dc: |                        |
    0xeffff9e0: |                        |
    0xeffff9e4: |                        |
    0xeffff9e8: |                        |
    0xeffff9ec: |                        |
    0xeffff9f0: |                        |
    0xeffff9f4: |                        |
    0xeffff9f8: |                        |
    0xeffff9fc: |                        |
    

    From line 9, we know that the address of i is equal to ip. Moreover, i's value is 11, so we can draw that in:

    0xeffff9cc: | ip = 0xeffff9fc        |
    0xeffff9d0: |                        |
    0xeffff9d4: |                        |
    0xeffff9d8: |                        |
    0xeffff9dc: |                        |
    0xeffff9e0: |                        |
    0xeffff9e4: |                        |
    0xeffff9e8: |                        |
    0xeffff9ec: |                        |
    0xeffff9f0: |                        |
    0xeffff9f4: |                        |
    0xeffff9f8: |                        |
    0xeffff9fc: | i = 11                 |
    

    Now, array is a pointer to the first element of the 10-element array. Since its value is 0xeffff9d0, we can draw in all ten elements of the array:

    0xeffff9cc: | ip = 0xeffff9fc        |
    0xeffff9d0: | array[0] = 0           |
    0xeffff9d4: | array[1] = 1           |
    0xeffff9d8: | array[2] = 2           |
    0xeffff9dc: | array[3] = 3           |
    0xeffff9e0: | array[4] = 4           |
    0xeffff9e4: | array[5] = 5           |
    0xeffff9e8: | array[6] = 6           |
    0xeffff9ec: | array[7] = 7           |
    0xeffff9f0: | array[8] = 8           |
    0xeffff9f4: | array[9] = 9           |
    0xeffff9f8: |                        |
    0xeffff9fc: | i = 11                 |
    

    Now we know all we need to know. Since &i equals ip, the first line of output is "&i: 0xeffff9fc." Next, from line 10 of the program, we know that ipp equals the address of ip. So the next line is:

    ipp: 0xeffff9cc, *ipp: 0xeffff9fc, **ipp: 0xb
    
    Note, that last word is 0xb, and not 11, because we are printing 11 in hexadecimal.

    Now, since a1 is a pointer to array[1], its value is 0xeffff9d4. Thus, our next line of output (after the blank line) is:

    a1: 0xeffff9d4, *a1: 0x1
    
    Finally, the statement ``a1 += 4'' is what's known as pointer arithmetic. It sets a1 ahead four ints. Therefore, it adds 16 to the value of a1 -- 16 because ints are 4 bytes: 4*4 = 16. After the statement it points to array[5]. Therefore, the last line is
    0 1 2 3 4 500 6 7 8 9 
    
    Here is the entire output:

    ip: 0xeffff9fc, &ip: 0xeffff9cc, array: 0xeffff9d0
    
    &i: 0xeffff9fc
    ipp: 0xeffff9cc, *ipp: 0xeffff9fc, **ipp: 0xb
    
    a1: 0xeffff9d4, *a1: 0x1
    0 1 2 3 4 500 6 7 8 9 
    

    The output can differ from machine to machine, but it totally dependent on the first line. Here it is compiled in 64-bit mode on my Macbook:

    UNIX> quiz
    ip: 0x7fff5fbfdc7c, &ip: 0x7fff5fbfdc70, array: 0x7fff5fbfdc30
    
    &i: 0x7fff5fbfdc7c
    ipp: 0x7fff5fbfdc70, *ipp: 0x7fff5fbfdc7c, **ipp: 0xb
    
    a1: 0x7fff5fbfdc34, *a1: 0x1
    0 1 2 3 4 500 6 7 8 9 
    UNIX> 
    

    At this point, I urge you to study question #3 from Midterm #1 in 2012, plus both questions in Midterm #2 in 2012. Reading memory and pointers is an important part of systems programming and these questions give you very good practice with it.

    String routines

    In C, we lose the ease of C++ strings, which is in some respects a pity. However, there are a lot of routines to help you create and manipulate strings in C. I go over many of them here:

    strcpy()

    char *strcpy(char *s1, char *s2);
    
    Strcpy() assumes that s2 is a null-terminated string, and that s1 is a (char *) with enough characters to hold s2, including the null character at the end. Strcpy() then copies s2 to s1. It also returns s1. Why would you return your first argument? We'll get to that when we show an easy way to implement strdup().

    Here's a simple program that uses strcpy() to initialize three strings and print them out (this is in strcpy.c):

    For those unfamiliar with "Give Him Six!", please see this, this or this.

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      char give[5];
      char him[5];
      char six[5];
    
      strcpy(give, "Give");
      strcpy(him, "Him");
      strcpy(six, "Six!");
    
      printf("%s %s %s\n", give, him, six);
    }
    

    It runs fine:

    UNIX> strcpy
    Give Him Six!
    UNIX>
    
    Suppose I try to copy a string that's too big. For example, look at strcpy2.c:

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      char give[5];
      char him[5];
      char six[5];
    
      printf("give: 0x%lx  him: 0x%lx  six: 0x%lx\n", 
              (unsigned long) give, 
              (unsigned long) him, 
              (unsigned long) six);
    
      strcpy(give, "Give");
      strcpy(him, "Him");
      strcpy(six, "Six!");
    
      printf("%s %s %s\n", give, him, six);
    
      strcpy(him, "T.J. Houshmandzadeh");
    
      printf("%s %s %s\n", give, him, six);
    }
    

    Clearly there's a problem with this -- the string "T.J. Houshmandzadeh" is much larger than five characters. Some compilers will compile this, but others, like the one on my Macintosh, takes issue with it:

    UNIX> gcc -o strcpy2 strcpy2.c
    strcpy2.c: In function 'main':
    strcpy2.c:21: warning: call to __builtin___strcpy_chk will always overflow destination buffer
    UNIX> 
    
    That's a wise compiler. However, compilers are not all-seeing and all-knowing. Let's fool it by writing our own wrapper around strcpy -- now it can't figure out the problem. This is in strcpy3.c

    #include <stdio.h>
    #include <string.h>
    
    void my_strcpy(char *s1, char *s2)
    {
      strcpy(s1, s2);
    }
    
    int main()
    {
      char give[5];
      char him[5];
      char six[5];
    
      printf("give: 0x%lx  him: 0x%lx  six: 0x%lx\n", 
              (unsigned long) give, 
              (unsigned long) him, 
              (unsigned long) six);
    
      strcpy(give, "Give");
      strcpy(him, "Him");
      strcpy(six, "Six!");
    
      printf("%s %s %s\n", give, him, six);
    
      my_strcpy(him, "T.J. Houshmandzadeh");
    
      printf("%s %s %s\n", give, him, six);
    }
    

    Now run it (again, your memory addresses may differ, but the interrelationship will be the same):

    UNIX> strcpy3
    give: 0xbfffe060  him: 0xbfffe050  six: 0xbfffe040
    Give Him Six!
    deh T.J. Houshmandzadeh Six!
    UNIX> 
    
    Take a minute and try to figure out what's going on. Look at the following picture of memory. When we start, space has been allocated for give, him and six:
                        |----4 bytes----|           
                   
                        |               |           
         six----------> |               | 0xbfffe040
                        |               | 0xbfffe044
                        |               | 0xbfffe048
                        |               | 0xbfffe04c
         him----------> |               | 0xbfffe050
                        |               | 0xbfffe054
                        |               | 0xbfffe058
                        |               | 0xbfffe05c
         give---------> |               | 0xbfffe060
                        |               | 0xbfffe064
                        |               | 0xbfffe068
                        |               | 0xbfffe06c
    
    Now, we make the first three strcpy() calls. At the point of the first printf() statement, memory looks like:
         six----------> |'S'|'i'|'x'|'!'| 0xbfffe040
                        | 0 |   |   |   | 0xbfffe044
                        |   |   |   |   | 0xbfffe048
                        |   |   |   |   | 0xbfffe04c
         him----------> |'H'|'i'|'m'| 0 | 0xbfffe050
                        |   |   |   |   | 0xbfffe054
                        |   |   |   |   | 0xbfffe058
                        |   |   |   |   | 0xbfffe05c
         give---------> |'G'|'i'|'v'|'e'| 0xbfffe060
                        | 0 |   |   |   | 0xbfffe064
                        |               | 0xbfffe068
                        |               | 0xbfffe06c
    
    Now, we make the call strcpy(him, "T.J. Houshmandzadeh"). What happens is that the entire string is copied to him, and this overruns the memory allocated for give:
         six----------> |'S'|'i'|'x'|'!'| 0xbfffe040
                        | 0 |   |   |   | 0xbfffe044
                        |   |   |   |   | 0xbfffe048
                        |   |   |   |   | 0xbfffe04c
         him----------> |'T'|'.'|'J'|'.'| 0xbfffe050
                        |' '|'H'|'o'|'u'| 0xbfffe054
                        |'s'|'h'|'m'|'a'| 0xbfffe058
                        |'n'|'d'|'z'|'a'| 0xbfffe05c
         give---------> |'d'|'e'|'h'| 0 | 0xbfffe060
                        | 0 |   |   |   | 0xbfffe064
                        |               | 0xbfffe068
                        |               | 0xbfffe06c
    
    So this means that him is indeed "T.J. Houshmandzadeh", but give has been modified as well, to be "deh". This accounts for the printout of:
    deh T.J. Houshmandzadeh Six!
    
    The bottom line is that when you modify memory that you have not allocated (as I did when I called strcpy(him, "T.J. Houshmandzadeh");), then strange things will happen. They have explanations, but until you figure it out, it will be confusing. If you're lucky, you get a segmentation violation or a bus error. If you're unlucky, you get wierd, inexplicable output. A corollary of this is that when you get a segmentation violation, a bus error, or wierd, inexplicable output, then chances are you have modified memory that you didn't allocate.

    strcat()

    char *strcat(char *s1, char *s2);
    
    Strcat() assumes that s1 and s2 are both null-terminated strings. Strcat() then concatenates s2 to the end of s1. I don't know what it returns -- read the man page if you care. Strcat() assumes that there is enough space in s1 to hold these extra characters. Otherwise, you'll start stomping over memory that you didn't allocate. Here is a simple example: (this is in strcat.c):

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      char givehimsix[15];
    
      strcpy(givehimsix, "Give");
      printf("%s\n", givehimsix);
      strcat(givehimsix, " Him");
      printf("%s\n", givehimsix);
      strcat(givehimsix, " Six!");
      printf("%s\n", givehimsix);
    }
    

    The output is predictable:

    UNIX> strcat
    Give
    Give Him
    Give Him Six!
    UNIX> 
    
    Look at strcat2.c. Can you explain why the output is the way that it is? Try filling memory as in the strcpy2 example above.
    UNIX> strcat2
    give: 0xbfffe060  him: 0xbfffe050  six: 0xbfffe040
    Give Him Six!
    deh T.J. Houshmandzadeh Six!
    deh Help! T.J. Houshmandzadeh Help! Six!
    UNIX> 
    
    C-style strings are a little more difficult to handle than C++ style string. For example, suppose you wanted to create a string with a given number of j's. In C++, you might write the following (makej.cpp):

    #include <iostream>
    using namespace std;
    
    int main(int argc, char **argv)
    {
      int i, n;
      string s;
    
      if (argc != 2) { fprintf(stderr, "usage: makej number\n"); exit(1); }
      n = atoi(argv[1]);
    
      for (i = 0; i < n; i++) s += "j";
      cout << s << endl;
    }
    

    Suppose you want to write the equivalent in C. It's a little more difficult, as you need to call malloc() first, to allocate the string. However, here it is (strcat3.c)

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(int argc, char **argv)
    {
      char *s;
      int i;
      int n;
    
      if (argc != 2) { fprintf(stderr, "usage: strcat3 number\n"); exit(1); }
    
      n = atoi(argv[1]);
      s = (char *) malloc(sizeof(char)*(n+1));
      strcpy(s, "");
    
      for (i = 0; i < n; i++) {
        strcat(s, "j");
      }
      
      printf("%s\n", s);
    }
    

    When you run them on small numbers, they appear equivalent:

    UNIX> makej 50
    jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
    UNIX> strcat3 50
    jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
    UNIX> 
    
    However, try them on a really big number. Here, I'm going to redirect standard output to /dev/null, which throws it away, and I'm going to time it with time:
    UNIX> time sh -c "makej 1000 > /dev/null"
    0.002u 0.004s 0:00.01 0.0%	0+0k 0+0io 0pf+0w
    UNIX> time sh -c "makej 10000 > /dev/null"
    0.002u 0.004s 0:00.00 0.0%	0+0k 0+0io 0pf+0w
    UNIX> time sh -c "makej 100000 > /dev/null"
    0.004u 0.004s 0:00.01 0.0%	0+0k 0+0io 0pf+0w
    UNIX> time sh -c "strcat3 1000 > /dev/null"
    0.002u 0.004s 0:00.00 0.0%	0+0k 0+0io 0pf+0w
    UNIX> time sh -c "strcat3 10000 > /dev/null"
    0.039u 0.004s 0:00.04 75.0%	0+0k 0+0io 0pf+0w
    UNIX> time sh -c "strcat3 100000 > /dev/null"
    3.468u 0.005s 0:03.47 99.7%	0+0k 0+0io 0pf+0w
    UNIX> 
    
    See the problem? The C++ string maintains the string's length, so concatenation is fast. In contrast, strcat has to find the end of the string at each call, which makes the program O(n2). We can fix it, since we know where the end of the string is. This is in strcat4.c:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(int argc, char **argv)
    {
      char *s;
      int i;
      int n;
    
      if (argc != 2) { fprintf(stderr, "usage: strcat4 number\n"); exit(1); }
    
      n = atoi(argv[1]);
      s = (char *) malloc(sizeof(char)*(n+1));
      strcpy(s, "");
    
      for (i = 0; i < n; i++) {
        strcat(s+i, "j");
      }
      
      printf("%s\n", s);
    }
    

    UNIX> strcat4 50
    jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
    UNIX> time sh -c "strcat4 100000 > /dev/null"
    0.003u 0.004s 0:00.01 0.0%	0+0k 0+0io 0pf+0w
    UNIX> 
    
    Such is life in C.

    strlen()

    long strlen(char *s);
    
    Strlen() assumes that s is a null-terminated string. It returns the number of characters before the null character. Strlen() is pretty obvious: (this is in strlen.c):

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      char give[5];
      char him[5];
      char six[5];
    
      strcpy(give, "Give");
      strcpy(him, "Him");
      strcpy(six, "Six!");
    
      printf("%s %s %s\n", give, him, six);
      printf("%ld %ld %ld\n", strlen(give), strlen(him), strlen(six));
    }
    
    

    Output:

    UNIX> strlen
    Give Him Six!
    4 3 4
    

    strcmp() and strncmp()

    int strcmp(char *s1, char *s2)
    int strncmp(char *s1, char *s2, int n)
    
    Strcmp() performs a lexicographic comparison of two strings. It returns 0 if they are equal, a negative number if s1 is less than s2, and a positive number otherwise. You will use strcmp() quite a bit in this class, because it's the easiest way to compare two strings.

    Strncmp() stops comparing after n characters, if the null character has not be reached yet. It's a good exercise for you to do the D2 250-point problem from Topcoder SRM 683 as a standalone program in C, using strncmp() and strlen() rather than the C++ string library. I'll do it in class.


    strchr()

    char *strchr(char *s, int c);
    
    Strchr() assumes that s is a null-terminated string. C is an integer, but it is treated as a character. Strchr() returns a pointer to the first occurrence of the character equal to c in s. If s does not contain c, then it returns NULL.

    Here is a simple program that prints out whether each line of standard input contains a space (this is in strchr.c):

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      char line[100];
      char *ptr;
    
      while (fgets(line, 100, stdin) != NULL) {
        ptr = strchr(line, ' ');
        if (ptr == NULL) {
          printf("No spaces\n");
        } else {
          printf("Space at character %ld\n", ptr-line);
        }
      }
    }
    

    I'm doing a little pointer arithmetic here -- ptr-line returns the number of characters between line and ptr. Here's an example of this running:

    UNIX> strchr
    UNIX> Jim
    No spaces
    Jim Plank
    Space at character 3
    James Plank
    Space at character 5
     HI!
    Space at character 0
         HI!!
    Space at character 0
    UNIX> 
    
    We can modify this to print out where all the spaces are. Check out strchr2.c:
    UNIX> strchr2
    Jim
    No spaces
    Jim Plank
    Space at character 3
    UNIX> Jim  Plank
    Space at character 3
    Space at character 4
      Give   Him   Six!!!
    Space at character 0
    Space at character 1
    Space at character 6
    Space at character 7
    Space at character 8
    Space at character 12
    Space at character 13
    Space at character 14
    UNIX> 
    
    Go over the code -- why do I say
            ptr = strchr(ptr+1, ' ');
    
    instead of
            ptr = strchr(ptr, ' ');
    
    If you don't know, copy the code, modify it, and see for yourself!

    tail10

    Suppose you want to write a program called tail10, that prints out the last ten lines of standard input. A straightforward design of this is to have an array of ten (char *)'s. Each time we read in a line of text, we set the next array entry to be that line. After filling in the 10th entry, we go back to the beginning. At the end, we print out the last 10 entries filled. Here is tail10a.c:

    #include <stdio.h>
    
    int main()
    {
      char line[1000];
      char *last10[10];
      int nlines, i;
    
      nlines = 0;
      while (fgets(line, 1000, stdin) != NULL) {
        last10[nlines%10] = line;
        nlines++;
      }
    
      if (nlines <= 10) {
        for (i = 0; i < nlines; i++) printf("%s", last10[i]);
      } else {
        for (i = nlines-10; i < nlines; i++) {
          printf("0x%x %s", last10[i%10], last10[i%10]);
        }
      }
      printf("Line is 0x%x\n", line);
    }
    

    Go over this until you understand what's going on. Unfortunately, it won't work right:

    UNIX> cat tailinput
    Line 1
    Line 2
    Line 3
    Line 4
    Line 5
    Line 6
    Line 7
    Line 8
    Line 9
    Line 10
    Line 11
    UNIX> tail10a < tailinput
    0x7fff5fbfd870 Line 11
    0x7fff5fbfd870 Line 11
    0x7fff5fbfd870 Line 11
    0x7fff5fbfd870 Line 11
    0x7fff5fbfd870 Line 11
    0x7fff5fbfd870 Line 11
    0x7fff5fbfd870 Line 11
    0x7fff5fbfd870 Line 11
    0x7fff5fbfd870 Line 11
    0x7fff5fbfd870 Line 11
    Line is 0x7fff5fbfd870
    UNIX> 
    
    Why? Because you're setting each entry of last10 to be line, which is getting overwritten at each fgets() call.

    One easy way to fix this is to make last10 an array of 10 arrays of 1000 characters each, and then to use strcpy(). This is in tail10b.c. It works correctly:

    UNIX> tail10b < tailinput
    Line 2
    Line 3
    Line 4
    Line 5
    Line 6
    Line 7
    Line 8
    Line 9
    Line 10
    Line 11
    UNIX> 
    

    tailany

    Now, suppose you want to modify tail10 so that it prints the last n lines, where n is defined on the command line. This is a problem because you can't statically allocate the last10 array as you did in tail10.c. This is an example where you can either In general, it is best to use the second approach. An example of where we use the first approach is whenever we assume that an input line is less than 1000 characters. For the purposes of this class, this is a reasonable assumption. However, if you are writing production code, you should never make such assumptions, because they will always come back to haunt you.

    So we'll use malloc(). This is C's version of new. Malloc(n) returns a pointer to n bytes of memory, given to you by the operating system. We say that this memory comes from the ``heap.'' Once you get this memory, you can use it anywhere -- you can pass it to and from procedure calls without worrying about it going away like you do local memory.

    Below, we show tailany1.c, which uses malloc() to allocate the array of 1000 character arrays. Note how the code basically looks like tail10b.c:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(int argc, char **argv)
    {
      char line[1000];
      char **lastn;
      int nlines, i, n;
    
      /* Error check the input */
    
      if (argc != 2) {
        fprintf(stderr, "usage: tailany1 n\n");
        exit(1);
      }
      n = atoi(argv[1]);
      if (n <= 0) exit(0);
    
      /* Allocate the array */
    
      lastn = (char **) malloc(sizeof(char *)*n);
      if (lastn == NULL) { perror("malloc"); exit(1); }
    
      for (i = 0; i < n; i++) {
        lastn[i] = (char *) malloc(sizeof(char)*1000);
        if (lastn[i] == NULL) { perror("malloc"); exit(1); }
      }
     
      /* Read the input */
    
      nlines = 0;
      while (fgets(line, 1000, stdin) != NULL) {
        strcpy(lastn[nlines%n], line);
        nlines++;
      }
    
      /* Print the last n lines */
    
      if (nlines <= n) {
        for (i = 0; i < nlines; i++) printf("%s", lastn[i]);
      } else {
        for (i = nlines-n; i < nlines; i++) {
          printf("%s", lastn[i%n]);
        }
      }
    }
    

    Note that when we call malloc(), we cast its return value to be the type we want. This is to keep the compiler happy -- malloc() returns a pointer -- we make the type cast statement to tell the compiler that we know what kind of pointer we want.

    This works just like we think it should:

    UNIX> tailany1 < tailinput
    usage: tailany1 n
    UNIX> tailany1 1 < tailinput
    Line 11
    UNIX> tailany1 2 < tailinput
    Line 10
    Line 11
    UNIX> tailany1 50 < tailinput
    Line 1
    Line 2
    Line 3
    Line 4
    Line 5
    Line 6
    Line 7
    Line 8
    Line 9
    Line 10
    Line 11
    UNIX> tailany1 10000000 < tailinput
    Line 1
    Line 2
    Line 3
    Line 4
    Line 5
    Line 6
    Line 7
    Line 8
    Line 9
    Line 10
    Line 11
    UNIX> 
    
    You'll note that if you run tailany1 10000000 < tailinput, it will take a little while. This is the time that it takes doing all those mallocs(). How many bytes is it allocating? 10000000*4 for lastn, and 1000 for each entry of lastn. That makes 40000000+10000000*1000 = 10040000000. That's roughly 10 gigabytes, which seems kind of wasteful. It is. Try it with a bigger number, and see if you can get tailany1 to break!

    Here's one solution. Instead of allocating arrays of 1000 characters, let's go back to using (char *)'s instead. Then after reading line, well call strdup() instead of strcpy().

    Strdup(s) basically does the following:

    char *strdup(char *s)
    {
      char *news;
    
      news = (char *) malloc((strlen(s)+1)*sizeof(char));
      strcpy(news, s);
      return news;
    }
    
    In other words, it gives you a new copy of s that it has malloc()'d for you.

    Tailany2.c uses strdup() -- check it out and make sure you understand how it works. Now you'll see that tailany2 1000000000 < tailinput runs much faster.

    There's one last problem with tailany2. That is that if nlines is much larger than n, then you are wasting a lot of memory because you are calling strdup() to copy lines, but you never give the memory back to the operating system. So your heap just grows and grows, when it doesn't have to. You can fix this with free(), which is just like C++'s delete. Free's syntax is:

    void free(void *ptr);
    
    You can free() anything that you have allocated with malloc() (or strdup(), since strdup() calls malloc()). tailany3.c fixes the problem by calling free() whenever you are about to overwrite a pointer that was allocated with strdup(). Check it out.

    sscanf

    Sscanf() is just like scanf, except it takes an additional string as its first parameter, and it "reads" from that string instead of from standard input. It returns the number of correct matches that it made. Thus, it is quite convenient for converting strings to integers and doubles. It is far superiour to atoi() and atof() because it lets you know when it fails, which is quite important.

    Here's an example program that reads lines of text from standard input, and attempts to convert them to ints and doubles. It is in sscanf1.c:

    #include <stdio.h>
    
    int main()
    {
      char buf[1000];
      int i, h;
      double d;
    
      while (fgets(buf, 1000, stdin) != NULL) {
        if (sscanf(buf, "%d", &i) == 1) {
          printf("When treated as an integer, the value is %d\n", i);
        } 
        if (sscanf(buf, "%x", &h) == 1) {
          printf("When treated as hex, the value is 0x%x (%d)\n", h, h);
        } 
        if (sscanf(buf, "%lf", &d) == 1) {
          printf("When treated as a double, the value is %lf\n", d);
        }
        if (sscanf(buf, "0x%x", &h) == 1) {
          printf("When treated as a hex with 0x%%x formatting, the value is 0x%x (%d)\n", h, h);
        }
        printf("\n");
      }
    }
    

    Here is an example of it running.

    UNIX> sscanf1
    10
    When treated as an integer, the value is 10
    When treated as hex, the value is 0x10 (16)
    When treated as a double, the value is 10.000000
    
    55.9
    When treated as an integer, the value is 55
    When treated as hex, the value is 0x55 (85)
    When treated as a double, the value is 55.900000
    
    .5679
    When treated as a double, the value is 0.567900
    
    a 
    When treated as hex, the value is 0xa (10)
    
    0x10
    When treated as an integer, the value is 0
    When treated as hex, the value is 0x10 (16)
    When treated as a double, the value is 16.000000
    When treated as a hex with 0x%x formatting, the value is 0x10 (16)
    
    UNIX> 
    
    The first four inputs should be straightforward. That last one is a little confusing, even to me, and the man page on sscanf() is not helpful. From that, it appears that %x and %lf recognize "0x" in the input and perform the proper conversion in hex. %d does not. I'll probe into it.

    Other useful procedures

    I don't go over these, but you'll use them from time to time. It's good to aware of them. Read their man pages.