Lab 1

Before attempting this, perhaps you should read a little about pointers, files and return values here
And read the man pages for getchar, putchar, gets, puts, fgets, fputs.

Proper Organization

You should create and then cd to a directory called
/cs/home/cse12345/Courses/2031/Labs/1/
and do the work for this lab in that directory. (Of course, the "cse12345" above is meant to be your login ID.)

For details on how to create these directories, see "Getting Organized" in lab 0.

Submitting Your Work

Whenever you are finished creating and testing a program, say called program.c, submit it with the command
submit 2031 lab1 program.c
The submit directory will open at the start of the lab and be closed at the end of the lab. We will not (in general) be mailing out comments on the submitted programs. The TAs (and sometimes the instructor) are available during the lab to comment on your work. Take advantage of this!

The Programs

  1. Consider the following program, which uses getchar() and putchar() to copy standard input to output a byte at a time. Start by reading the man page for getchar() and putchar. That's
    man -s 3 getchar
    since the C library functions are in section 3 of the manual.

    Here's the program. It should look familiar, as it's on page 17 of K&R.
    /* copies standard input to standard output a byte at a time */
    
    #include <stdio.h>
    
    int main(void)
    {
        int c;
    
        while((c = getchar()) != EOF)
            putchar(c);
    
        return 0;
    }
    
    Make a copy of this program and call it q1.c. Compile (to q1) and test it on some files you create. In the following example, the input to q1 is redirected to come from a file called "infile" and the output is redirected to go to a file called "outfile". If q1.c works correctly, the two files should be byte-for-byte identical. We test this using the diff command. (Of course, you'll now hurry off and look at the man page for diff.) diff returns silently if the two files are identical, and complains if they are not.
    $ 
    $ cat infile
    Here is a testfile
    with some
    stuff in it.
    $ 
    q1 < infile > outfile
    $ diff infile outfile
    $ 
    
    Change one byte in one of the files and call diff again to see what happens.

    You can see the bytes (expressed as 3 octal digits) in a file by using the command od -cb filename. od stands for "octal dump" (... man page ... ).
    $ 
    $ od -cb infile
    0000000   H   e   r   e       i   s       a       t   e   s   t   f   i
            110 145 162 145 040 151 163 040 141 040 164 145 163 164 146 151
    0000020   l   e  \n   w   i   t   h       s   o   m   e  \n   s   t   u
            154 145 012 167 151 164 150 040 163 157 155 145 012 163 164 165
    0000040   f   f       i   n       i   t   .  \n
            146 146 040 151 156 040 151 164 056 012
    0000052
    $
    
    1. Copy program q1.c to q1a.c and change q1a.c so that in the output it replaces all small letters with the corresponding capitals and leaves an additional empty line between each of the existing lines, like this:
      HERE IS A TESTFILE
      
      WITH SOME
      
      STUFF IN IT.
      
      
      Don't worry if you add an extra blank line after the last line. See the man page for toupper. See appendix B2 in the text.

    2. Make a copy of q1.c called q1b.c. Change it so that the variable c is declared as unsigned char c;. Show that the new program is no longer guaranteed to correctly copy input to output.
      Note: To kill a running program, type in ctrl-c

    3. Make a copy of q1.c called q1c.c. Change it so that the variable c is declared as signed char c;. Show that the new program is no longer guaranteed to correctly copy input to output. This is a bit more difficult and you may want to skip this for now and try it at home.

  2. Write a C program called q2.c that uses gets and puts to copy standard input to standard output a line at a time. Assume that the lines in a file are never longer than 20 characters, including the newline character at the end. First, of course, see gets and puts in the text and on the man pages.

  3. Write a program called q3.c that demonstrates why you should avoid using gets. Start by making a copy of q2.c. Add declarations for 2 ints, one before the declaration of the char array, and one after, as in:
    int n1 = 0xb76f81c4;
    char buf[9];
    int n2 = 0x81ef3ba2;
    
    You can print out the addresses where n1, buf and n2 are stored like this:
    printf("  n1 starts at %p\n", &n1);
    printf("buf starts at %p\n", buf);
    printf("  n2 starts at %p\n\n", &n2);
    
    Then, after the while loop used to read and write lines using gets and prints, add 2 printf statements to print out the values of n1 and n2.

    Try running this program when the longest input line, counting the newline character, is 9 bytes, then 10, 11, 12, 13, 14. Can you explain what happens?

    Here is a description of what is going on with q3: memory_layout

  4. Write a C program called q4.c that uses fgets and fputs to copy standard input to standard output a line at a time. Use a buffer size of 9, but experiment with longer lines in a file. First, of course, see fgets and fputs in the text and in the man pages. The simplest thing to do is to make a copy of q2.c and replace the calls to gets and puts with calls to fgets and fputs. Compare the output to the input file using diff. Then change q4.c to be a copy of q3.c and change calls to gets and puts to calls to fgets and fputs. Are the results the same or different when you run q4 with input from the file used to test q3?

    When you get this working, forget all about gets and puts. Never use them again. Only use fgets and fputs.

  5. Write a C program called q5.c. It should read ints from standard input until a non-int or end-of-file is encountered. It should echo each int alone on a line and finally output the total, as in the following two examples.

    BUT FIRST think about what kind of input to use: character oriented (getchar), token oriented (scanf) or line oriented (fgets). If you use character oriented, then you need to ignore characters that aren't digits and somehow build up the integers as you read - no fun. If you use line oriented input, you'll get the line, stored in a char array, and then have to extract the integers from that - easier than getchar, but still a lot of trouble. Clearly, scanf is the way to go here - it skips over whitespace and grabs the next token as an int (or whatever you tell it).

    So - given a programming task, always think about what is the right way to deal with I/O. Not doing that is one of the most common beginner mistakes. And now on to the q5 example runs.
    $ 
    $ cat numbers
      4    6
    3       2  1
    
    10
    
    
    $ q5 < numbers
    4
    6
    3
    2
    1
    10
    total = 26
    $
    $
    $ cat numbers2
      4    6
    3       2  1
    
    10Hello
    
    500 1000
    $ 
    $ q5 < numbers2
    4
    6
    3
    2
    1
    10
    total = 26
    $ 
    

  6. Write a C program called q6.c. It should behave like q5.c, except that it should skip over everything that's not in 0 ... 9 and just pick out the ints. The output should be like that for q5.c. See "scanf assignment suppression" and "[^ ... ]" in scanf conversion. Here is an example:
    $ 
    $ cat numbers3
    %&4**XY6
    3~~=VR)[2*n1
    a*(b
    10Hello
    
    
    $ 
    $ q6 < numbers3
    4
    6
    3
    2
    1
    10
    total = 26
    $ 
    

End of the exercise