Lab 1 - Introduction to C

Lab goals:


Before You Start ...

For this course, you will need to have a Rice NetID-type account to be able to log into Rice's CLEAR servers. If you do not have one, you can apply for one here.

For this course, you will also need a github account. Many of you already have a github account. If you do not have a github account, please go to the github website and sign up.

Register your github id with COMP 321

To ensure that we can properly attribute your work for grading, please enter your Name, Rice NetID, and github ID on this form.

Register for COMP 321 on Piazza

This semester, we will be using Piazza, a web-based platform that will allow you to post questions about the course material and to receive answers from the instructors, from the TAs, and from your fellow classmates.

If you have not already done so, please register for COMP 321 on Piazza by going to the registration page. You will need to register using your @rice.edu email address.

Throughout the semester, check Piazza frequently for any new questions, answers, announcements, or other information. To do so, go to the COMP 321 Piazza page, log in to Piazza if you are not already logged in, and click on "Q & A" at the top of the page, if it is not already selected.

Computer Pragmatics

Your programming assignments in this class must be written and tested on the CLEAR servers. However, you need not come to the Laboratory to login to the CLEAR servers. You may login using ssh from your own personal laptop or desktop computer, Moreover, it doesn't matter whether your computer is on-campus or off-campus. You can use your laptop as an X11 terminal to log into CLEAR's Unix servers through ssh.clear.rice.edu.

To accomplish this task, you will need two software components:

  1. An X11 server
  2. An ssh client
The specifics for these two components will differ, depending on whether your computer is a Mac or a Windows machine.

NOTE: If you want to use the Symonds II Lab workstations (this is the room you are in for the lab, by the way), they are Windows machines. Consequently, you should follow the instructions below for Windows machines when using them.

Using a Mac to log into CLEAR

For Mac users, you need to make sure that X11 is installed. (Note that X11 is not included by default with macOS.)

  1. Bring up the terminal application. (Use spotlight if you do not know where it is.)
  2. At the terminal's command line, type:
    	xterm &
Did another window appear, or did you get an error?

If another window appeared, then X11 is already installed on your Mac. Proceed to the next paragraph. Otherwise, if you got an error, then you need to install X11. The X11 server and client libraries for macOS are available from the XQuartz project. Restart your computer after the installation completes.

Once you have X11 installed on your Mac, follow these steps to access the CLEAR system:

  1. Bring up the terminal application again.
  2. At the terminal's command line, type:
           ssh -Y yournetid@ssh.clear.rice.edu
    to log into CLEAR.
Congratulations! You are now logged into the CLEAR system!

Note 1: When logging in from your own personal computer running macOS or any other variant of Unix, your username on that computer may be different than your Rice NetID.

Note 2: When entering commands, you must precisely duplicate the given capitalization. For example, substituting a lower case -y in place of the upper case -Y can have the opposite of the intended effect.

Using a Windows machine to log into CLEAR

If your computer's operating system is Microsoft Windows-based, then you will likely need to download and install the PuTTY program (PuTTY serves as the ssh component) and Xming (Xming is the X11 Server component). There are other ssh clients and X11 servers available for Windows, but these are the ones we recommend for this class.

To log into CLEAR's Unix servers, we will use Xming and SSH Secure Shell from PuTTY. SSH connects to the remove servers, and Xming is used to forward GUIs from programs on the remote servers for display on your local screen.

The Xming program must be run first. To do this, click Start, type "xming", then run the program. When you run the program, it adds an X icon into your desktop tray.

After you start Xming, you will need to use PuTTY to log into CLEAR's Unix servers. To do this, click Start, type "putty" , then run the program.
You will see two panes. Your cursor should be positioned in the right pane inside a text box labeled "Host Name".

Type ssh.clear.rice.edu in this text box.

Next, in the left pane find a tree node labeled SSH.
There will be a "+" sign next to it.
Click on the "+" sign.
This opens up more choices in the tree. Click on the "X11" option.
The pane on the right then has a check box labeled "Enable X11 Forwarding". Click that checkbox to enable it.

Finally, click the "Open" button at the bottom near the right corner. This will bring up a terminal window for your login to the CLEAR system.
Enter your Rice NetID and password.

Congratulations! You are now logged into the CLEAR system!


C Survival Skills, Part 1

Like with Java programs, you will normally follow a three-step process:

  1. Write a C program.
  2. Compile the program.
  3. Run the program.
Of course, as you already know from previous COMP classes, writing a program is itself a multi-step process. Thus, you should expect to loop through these three steps as you finish, fix, and/or improve the program.

  1. Writing will be done in an editor. We recommend emacs.

    To run emacs, you may use the following command:

             emacs

    You will not be able to use the shell now, as it waits for the process(emacs) to exit. To get the shell back immediately after starting emacs, add & to the end of the command.

             emacs &

    If you are using having a problem with the fonts, you can often fix it using the following command instead:

             emacs --font fixed &
    A new emacs window should now appear on your PC's desktop. In this emacs window, you should open a file with a name ending in .c for your new program. To open this file, go to the File menu and select Open File.... This will start a new buffer within the emacs window for editing your program.

    As a shortcut, you can put the file name on the command line, such as:

             emacs program.c &
    where program.c is the name of the file that you wish to edit.

    In emacs, by default, any character that you type will be inserted into the buffer at the location of the cursor. So, to enter your program, you may simply begin typing it in. If you make a typo, you may erase the character preceding the cursor, by depressing the Backspace key. To erase the character underneath the cursor, depress the Delete key.

    To move the cursor around the buffer one character or line at a time, you may use the arrow keys. To move the cursor up or down a page at a time, you may use the Page Up or Page Down keys.

    We recommend emacs because its "C mode" will give you some help in editing C programs. For example, the Tab key will automatically insert the appropriate number of spaces to indent the code on the current line.

    Finally, after you have finished entering your program into the buffer, you must command emacs to save the buffer to the file that you previously opened. To do so, go to the File menu and select Save.

  2. Compiling may be done either at the Unix prompt, from within emacs, or at the control of a compilation manager like make.

    In Unix, an appropriate command would be

             clang -Wall -Wextra -Werror program.c -o program
    This command and its options say the following:

    • Use the clang compiler.
    • Enable all standard and some extra warnings.
    • Treat any warning like an error and reject the program.
    • Use the single source code file program.c.
    • Output the resulting executable in a file program.

    We recommend that you use -Wall and -Wextra because they instruct the compiler to check for common programming errors, like using uninitialized variables. Technically, programs that provoke warnings are not illegal C programs, but they are unlikely to produce correct results. You'll see and use more compiler options later. Don't be surprised if the compiler finds lots of errors in your code and doesn't produce an executable.

    Compiling within emacs (using Tools->Compile...) uses this same kind of command, but provides some additional features for your convenience. In particular, emacs will convey you to the location of each of the errors that clang found in your source code file.

    Nonetheless, most compilation in Unix is usually done under the control of make. A later lab will fully explain make, but even the first projects will set things up for you to use make without you needing to know the details of its operation.

  3. Running your program is then simply a matter of typing its full path name (e.g. /a/b/c/program), or relative name (e.g. ./progs/program) at a Unix prompt.
    Relative names are often much shorter and easier to type. In the Unix shell, a period (".") is shorthand for the current directory, so prefixing "./" on a file name means to look for it in the current directory.
    For example, similarly, 2 periods ("..") indicate the parent directory, 1 level up.

            ../program

    Note: If a single name is given to the shell (e.g., program), the shell uses a search path to look for the program to run.
    A search path is simply a list of directories to look through to find executable programs of the given name.
    If the "current directory", or ".", is not in your Unix command search path, then you will need to prefix "./" to your program's name to run the program. For example:

             ./program
    Otherwise, an error message such as the following will be printed:
             program: Command not found.
    To check your Unix command search path, you can enter the following command:
             echo $PATH
    For programs in the current directory, running them using the "./" prefix as above (e.g., "./program") is always the best approach, in any case.

A Quick Exercise

Type or copy-and-paste the following small program into emacs. Then compile and run it.

     #include <stdio.h>

     int
     main(void)
     {

             printf("Isn't this a silly program?\n");
             return (0);
     }

Make sure you don't name your program the same as any of the standard Unix programs or commands. Otherwise, you're likely (depending on your $PATH) to run the standard program or command and not your own program. To test whether a name is used, e.g., test, try the command

     which test


Brief Overview of the github Workflow for COMP 321

Your next two exercises in this lab will use some starting code that we will provide to you via github. In addition, you will use github to turn in your programming assignments and laboratory exercises. To accomplish these tasks, you must have some rudimentary git and github skills.

For this class, the first step for any assignment or exercise is accept an invitation to the assignment/exercise from github. For lab01, please point your browser to this link.

The above link will eventually take you to a page labeled RICE-COMP321-SPRING19.

The page will direct you to accept the invitation. The page will also specify a repository that is unique to you for this specific assignment. Please accept the assignment invitation.

NOTE: You may have to login to github to see the invitation. If so, you will be prompted to do so at the top of the page.

After accepting the assignment, you will then see a page confirming that you have accepted the assignment. There will be a link to your personal repository for the assignment on that page. Click the link to your personal repository.

Your private repository link has a green button labeled Clone or download. Click the button.

You should now see a text field with a URL, and a small box to the right containing a clipboard icon.
Click the clipboard icon to put your personal repository for the assignment into your clipboard.
You will use this URL to clone your personal repository onto the CLEAR system.

Login to the CLEAR system if you have not already done so. In your home directory, you will type the following:

    git clone  [Paste your URL from the clipboard here]
  
You will be prompted for your github username and password.

Once the clone operation is complete, you will have a directory named
lab-1-introduction-to-c-[YOUR github userid]
Confirm that you have the directory by typing ls on the command line. (ls is the Unix command to list files)

Now, change your working directory to your newly cloned directory by typing

cd lab-1-introduction-to-c-[YOUR github userid]
The cd command is short for Change directory. Now that you are inside your working directory, type ls again. You will see 2 files of starting code:

      fact_loop.c
      fact_rec.c
  

At this point you will edit, compile, and run your files several times. When you have completed a unit of work, you will want to commit your changes to your local repository. In addition, after a few commit steps, you will want to push your changes back to the remote repository. (We are being vague here, as the amount of work you do before a commit or push is a judgement call.)


A Brief Introduction to git Concepts

The git program is a complex system with hundreds of operations and variations. For this lab, however, we will stick to the 6 most fundamental operations. For the most part, these operations should suffice for most of the class. We will supply you with more information if and when it becomes necessary.

The fundamental object that git maintains is called a repository. For the moment, think of a repository (or repo for short) as a kind of augmented directory. The repository contains source code files, augmented with a history of changes. This means that you can go back to any version of a file that has been committed to the repository.

The git system distinguishes between local and remote copies of a repository. The normal workflow begins by cloning a remote repository, makes several commits to the local copy, then pushes the changes back to the remote repo.

One other wrinkle in the git workflow involves local commits. Rather than a single operation to accomplish a local commit, git uses 2 operations to accomplish this task. The first stage is an add operation, followed by the actual commit operation. The user adds the files to be committed in stage 1, followed by a single commit to commit all of the added files at 1 time.


An Example Workflow

In this section, we will give some details on the git operations needed to enact a simple (and typical) COMP 321 workflow.

Step 0. Clone the starting repo (You have already done this step for this lab, presumably)

      git clone  [Your Personal Assignment Repository]
  

Step 1. If you have not done so already, cd to your local directory (repo).

    cd [Your Local Assignment Directory]
  
Work in your local directory; perform some edit-compile-test cycles.

Step 2. When at a good point, do a local commit.

Step 2.1: See what has already been added using


	git status
  
Step 2.2: Add all files we want to commit, both files that have changed since last commit as well as newly created files.
	git add  [Changed files + new files]
  
Step 2.3 (Optional): Confirm the status

	git status
  
Step 2.4: Commit the changes to the local repo
      git commit -m'[Brief 1 line message describing the commit]'
  

You may repeat steps 1 and 2, before deciding to push the local commits to the remote repository.
(You may also, instead, decide to push after every local commit, if you want to.)

Step 3. Push local repository changes back to remote repository


	git push origin master
  
PLEASE NOTE: You must push your final result!
This is how we are able to grade your submissions!

You can push as many times as you want. We always take your latest version (up to the deadline) for grading.

One other operation you may want to use will list the commit messages from the history.


	git log
  

Finally, in true Unix fashion, the git program supplies general help


        git help
You can also get help with a specific git command. For example

        git help commit
shows help for the commit command. Other git commands also have help.


C Fundamentals

Don't forget what you've learned before, especially about designing and testing programs. For most programs and functions, you should follow all the same design steps as you learned in previous COMP classes.

Now we need to see some basic C syntax. To begin, you'll write small programs that are just transliterated from ones written in more familar languages, like Python. In fact, the syntax should be familiar to those of you who just took COMP 215, because Java's syntax is based on that of C. For brevity, we'll omit the steps you should be using in designing the programs, and show only the final code.

A simple recursive function

For now, we will use numbers as our only data. Examples with more interesting kinds of data will be presented in the upcoming lectures and laboratories.

Let's write sum_upto, which computes the sum of the numbers 0...n. In Python, we could write

     def sum_upto(n):
         """ 
         Returns 0 + ... + n.
         """
         if n == 0:
             return (0)
         else:
             return (n + sum_upto(n - 1))
A C transliteration would be
     /* Returns 0 + ... + n. */
     int
     sum_upto(int n)
     {

             if (n == 0)
                     return (0);
             else
                     return (n + sum_upto(n - 1));
     }

Some remarks:

A complete program

Like Java objects, C functions don't stand on their own as complete programs. We need a main() function to start the program. To make it useful, we also need at least a little bit of output.

A sample main() for the above sum_upto():

     #include <stdio.h>

     /* Returns 0+...+n. */
     int
     sum_upto(int n)
     {

             if (n == 0)
                     return (0);
             else
                     return (n + sum_upto(n - 1));
     }

     int
     main(void)
     {
             int n1 = 3;
             int n2 = 5;

             printf("The sum of 0..%d = %d.\nThe sum of 0..%d = %d.\n",
                 n1, sum_upto(n1), n2, sum_upto(n2));
             return (0);
     }

Some remarks:

C Recursion Exercises

Now you will write a couple of tiny C programs yourself.

  1. Write a recursive factorial function fact_rec().
    We have provided test code for you (shown below) in the file named 'fact_rec.c'

         #include <stdio.h>
    
         /* Put your function here. */
    
         int
         main(void)
         {
                 int n = 5;
    
                 printf("The factorial of %d is %d.\n",
                     n, fact_rec(n));
                 return (0);
         }
         
    Compile and run your resulting code several times to make sure it works with different values of n.

  2. Write a recursive program for multiplying two non-negative numbers using only addition.

    Name your program file mult_rec.c.
    Be sure to push it back to your assignment repo.

Sample solutions: factorial, multiply.


Loop-based Programming

In low-level programming, loops are far more common than recursion.

In these examples, the corresponding pieces of code are colored in like colors.

  1. Initialize the loop counter.
  2. Initialize the accumulator.
  3. Repeatedly,
    1. Test whether we should continue.
    2. Update the accumulator.
    3. Update the loop counter.
  4. Return the accumulator.

One way to rewrite the sum_upto function in Python to use loops is

     def sum_upto(n):
         """ 
         Returns 0 + ... + n.
         """
         total = 0
         for i in range(n + 1):
             total += i
         return total

Two C loop-based versions are
     /* Returns 0+...+n. */
     int
     sum_upto(int n)
     {
         int total = 0;
         int i;

         for (i = n; i > 0; i -= 1)
             total += i;
         return (total);
     }
     /* Returns 0+...+n. */
     int
     sum_upto(int n)
     {
         int total = 0;
         int i = n;

         while (i > 0) {
             total += i;
             i -= 1;
         }
         return (total);
     }

Some remarks:

C Loop Exercises

Now you will write the same two programs as before, except using loops.

  1. Write a for-loop factorial function fact_for() and a while-loop version fact_while().
    We have provided test code for you (shown below) in the file named 'fact_loop.c'

         #include <stdio.h>
    
         /* Put your functions here. */
    
         int
         main(void)
         {
                 int n = 5;
    
                 printf("Factorial of %d is %d = %d.\n",
                     n, fact_for(n), fact_while(n));
                 return (0);
         }
         
    Compile and run your resulting code several times to make sure it works with different values of n.

  2. Write a loop-based program for multiplying two non-negative numbers using only addition.

    Name your program file mult_loop.c.
    Be sure to push it back to your assignment repo.

Sample solutions: factorial, multiply.


Function Prototypes

C requires function prototypes in order to know the arguments and return types of a function that has not yet been defined.

So, the following code will compile and run correctly:

     #include <stdio.h>

     /* Returns 0+...+n. */
     int
     sum_upto(int n)
     {
             int total = 0;
             int i;

             for (i = n; i > 0; i -= 1)
                     total += i;
             return (total);
     }

     int
     main(void)
     {
             int n = 5;

             printf("Sum of 0 ... %d is %d.\n",
                 n, sum_upto(n));
             return (0);
     }

But, this version with the functions reordered will not:

     #include <stdio.h>

     int
     main(void)
     {
             int n = 5;

             printf("Sum of 0 ... %d is %d.\n",
                 n, sum_upto(n));
             return (0);  /* no error */
     }

     /* Returns 0+...+n. */
     int
     sum_upto(int n)
     {
             int total = 0;
             int i;

             for (i = n; i > 0; i -= 1)
                     total += i;
             return (total);
     }

The use of a function prototype will allow the compiler to know that the function sum_upto is going to be defined later:

     #include <stdio.h>

     int sum_upto(int n);

     int
     main(void)
     {
             int n = 5;

             printf("Sum of 0 ... %d is %d.\n",
                 n, sum_upto(n));
             return (0);
     }

     /* Returns 0+...+n. */
     int
     sum_upto(int n)
     {
             int total = 0;
             int i;

             for (i = n; i > 0; i -= 1)
                     total += i;
             return (total);
     }

Some remarks:


C Survival Skills, Part 2

We will not be introducing you to all features in C. You should use a C reference book. Also, the online manual pages describe all the standard C library functions.

Quick Exercise:

Look at the online manual page for a C library function. For example, try

     man strcpy
or
     man fprintf
Sometimes, the Unix shell uses the same name as C for a function, and man finds the shell-version by default. So, you can also specify which section of the manual to look in, e.g.,
     man 3 printf
Section 3 of the Unix manual contains documentation on general programming library functions.

You may notice some (usually small) differences between the manual pages on different servers, because they are running slightly different OS versions.