C Introductory Lab

This is an individual assignment.

Setting up GitHub and git

Before getting started with C programming, get set up with GitHub to submit your code. Do the following:

  1. Log into GitHub, using the username that you sent me back in the very first assignment.
  2. You should have received an email invitation to join our course GitHub organization. That should have been sent to whatever email address you specified when you signed up for GitHub. Make sure to reply to that invitation before proceeding, or you won't be able to find our course repositories. If you can't find that email, you should also be able to find the invitation after logging into GitHub by visiting the URL
    https://github.com/carleton251-term
    ... where "term" in the above is a single letter indicating which term we are in, and a two digit number indicating the year. For example, if you are taking this class in the fall of 2015, the URL would be
    https://github.com/carleton251-f15
  3. On the right hand side, in a list called "Repositories you contribute to," you should see one called carleton251-term/username-c_lab. (username is your own username, term is this term). Click on it. Contact me right away if the repository isn't there.
  4. Now you need to clone the repository to your local account. Fint the box on the page that shows you the link for your account, and make sure "HTTP" is selected instead of "SSH". (You can use SSH if you want to figure out how to set it up on your own. HTTP is easier to configure.) Highlight the https link shown, and copy it to the clipboard. Then, in your Linux desktop, open up a terminal window. Navigate to a subdirectory if you like. After navigating to a safe directory to keep your code in, type "git clone", then a space, then paste in the URL from the github page that you copied. For example, I would type the following, though your username and term will be different:
              git clone https://github.com/carleton251-term/dmusican-c_lab.git
            
    If all goes well, you should receive an empty directory, ready for your code.
  5. To make sure that git and GitHub are up and running, create a readme.txt file in your new directory. Put some kind of text in it. Then issue the following two commands:
              git status
              git add readme.txt
            
    The first command should show you that you have a new file that is not yet tracked by git. The second command will add it for tracking.
  6. Next, issue these two commands:
              git status
              git commit -am "I just added a readme.txt file."
            
    The first command should now show that you have a file with changes ready to be committed. The second command will commit the changes to your local repository. If this is your first time using git, you may receive an warning that your name and email address were configured automatically. To make that message go away in the future, enter in the two git config messages that it will show you.
  7. Once you have successfully added your file and committed it to the local repository, push it to GitHub by issuing the following command:
              git push
            
    This tells git to push your changes to GitHub. If this is your first time pushing, you might get a warning message claiming that "push.default is unset." If so, follow the recommendation it provides to "adopt the new behavior now." and copy and paste
              git config --global push.default simple
            
    into the terminal window. Then try
              git push
            
    again, and hopefully all will go well. If it works, go back to GitHub in your browser, and within your repository, click on the readme.txt file You should hopefully be able to see your file there.

    Note: if this is your first time pushing, you may have to instead type
              git push origin master
            
    If pushing doesn't seem to work, try it instead. You should only need to add "origin master" the first time.
  8. Hopefully the above all works as expected; if not, ask for help before going further.

Compiling and running your first program

You'll need a text editor and a C compiler to work. We've got a couple of text editors in the virtual machine; emacs, vim, gedit, JEdit, and Brackets are all there. You can also install others if you like. For the C compiler, you'll use clang, which is already installed.

Download hello.c to your git repository folder, then add it from the prompt by issuing the commands

      git status
      git add hello.c
      git commit -am "Adding my new hello.c"
    

Once you have done so, open up the file in your text editor. This program (unsurprisingly) is supposed to print "Hello, world!" to the screen.

Compile the program to a binary executable by running the following in the terminal:

$ clang -o hello hello.c

This command should produce a file name hello. Try running it.

$ ./hello
Hello, world!

EXERCISE 1: Change the program to print "Hello, <your name>!". Note that you will have to recompile the program before running it again. When complete, commit your changes with git, and then push your changes to GitHub. Verify that you can see your updates on the GitHub website.

Printing

The first non-commented line of hello.c is: #include <stdio.h>. This directs the C compiler (or technically, a subprocess called the C preprocessor) to include a header file named stdio.h that lists the signatures of standard IO functions, such as printf.

In a terminal window, type man 3 printf. All of the functions in the standard C library are documented in the system manual. (The 3 is because there's also a bash command named printf, so you have to specify which manual to use.) The man page tells you which header file to include to get access to that function (in this case, stdio.h), as well as documenting the interface and behavior of the function of interest. In this case, the man page also includes several similarly-named functions such as sprintf and fprintf. Read through the man page for printf.

printf is similar to Python's string formatting operator, in that it accepts a string that includes format specifiers to print integers, floats, etc. Download the file printing.c, which contains a number of different examples of print formats.

Here are common C types and their printf formatting specifications:

Format specifier Type
%c char
%i or %d int
%li or %ld long
%f float or double
%s string (really a char *)
%p pointer (e.g. int *)

EXERCISE 2 (Exercise 3.4 from Kochan): Add code to printing.c that subtracts the value 15 from 87 and prints the result, together with an appropriate message. Add your file with git, commit your updates, and then push your changes to GitHub. Verify that you can see your updates on the GitHub website.

User Input

You can get user input using the scanf function (man scanf):

int i;
scanf("%i", &i);
printf("You entered: %i\n", i);

The first argument to scanf is a string containing formatting specification(s) for the type of input expected. In this case we expect an int.

The second argument should look pretty weird to you. How is scanf able to modify the value of i? What's that ampersand symbol? The answer is that we're actually passing a pointer to i's location, not i itself - so the value of i can be modified from within scanf. There will be much more on pointers later.

EXERCISE 3: Write a program temperature.c that asks the user for a temperature in Fahrenheit and prints the temperature converted to Celsius. The relevant forumla is: temp_c = (temp_f - 32) * 5/9. For example:

What is the temperature in degrees Fahrenheit? 42.5
42.500000 degrees Fahrenheit is 5.833333 degrees Celsius.

(If you're interested: there are variants on the formatting specifiers that limit the number of zeroes. Read up on that further if you like.)

Add, commit, and push to GitHub.

Loops and if statements

C has basically the same syntax for if statements, for loops, while loops and do/while loops as Java. However, there are a couple of differences you should be aware of:

EXERCISE 4: What happens if you enter something other than a number when prompted for the temperature for your answer to Exercise 3? Read the "Return Values" section of man 3 scanf. Modify temperature.c to check for bad input and exit with an appropriate error message. Use git to add, commit, and push to GitHub when complete.

Exercise 4 is the last exercise that is due in the first part of this lab. You have now pushed a number of commits to GitHub, and you'll follow with more for the remaining exercises. The graders and I need to know which commit is the one that we should use for grading. This is handled well in git with tags. Issue the following commands:

      git commit -am "All my changes are committed, I may have already done this"
      git push
      git tag firsthalf
      git push --tags
    

Look on GitHub after the push, and if you click on your most recent commit, you should be able to see the tag listed there.

You should wait to tag until you're sure you have committed the version you want us to grade. That said, in the unlikely event that you goof and realize that you want to commit a newer version for us to grade, you'll need to use another tag. Reusing the same tag for a different commit is generally a really bad idea. If you need to tag another commit, append a ".1" (or whatever version number you're up go. For example:

      git commit -am "Oops: grade this one, I really mean it"
      git push
      git tag firsthalf.1
      git push --tags
    

Here's a long tangent. Note that GitHub sort of displays the tags wrong: even though in Git a commit is associated with only a single tag, GitHub will show you that same tag if you click on all commites that precede it. (Note that this is making a distinction between Git, the command-line tool, and GitHub, the webserver.) GitHub is trying to be helpful by showing you all commits that contribute to a release, as that's how tags are often used, but GitHub is ultimately doing this wrong. If you want to see your tags at the command prompt, you can use:

git log --tags --decorate=full

EXERCISE 5: Write a program guess.c that picks a random number between 1 and 100 and has the user repeatedly guess the number in a while loop. For random numbers, use man 3 random:

#include <stdlib.h>
#include <time.h>

...

srandom(time(NULL));
long num = random();

For example, a run of your program might go like this:

$ ./guess.exe
Guess a number: 50
Too low! Guess again: 75
Too low! Guess again: 87
Too low! Guess again: 93
Too low! Guess again: 97
Too high! Guess again: 95
Too high! Guess again: 94
Correct! The answer was 94.

(Added on 10/5/15): For this exercise, you do not need to check for invalid input if you don't wish to.

Use git to add, commit, and push to GitHub when complete.

Arrays

You can declare an array in C by giving the type and size of the array. (See arrays.c for the full code listing.)

int array[10];

This allocates space on the stack for an array containing 10 integers.

Like Java, you can read and write elements of the array using [] notation.

for (int i = 0; i < 10; i++) {
    array[i] = i;
}

Unlike Java, there's no way to find out the length of an array, so you need to keep track of an array's length yourself. And if you write past the end of an array, there's no check or "out of bounds error" - it will just modify whatever happens to be next in memory! Try uncommenting the line:

array[10] = 5;

in arrays.c and observe how the value of x changes (note clang issues a warning not to write past the end of the array, but the program should still compile). What does this imply about the layout of main's stack frame in this program?

EXERCISE 6: Add code to arrays.c to calculate the sum of the array and print it. Add, commit, and push to GitHub when complete.

Pointers

The variable array is actually a pointer to the first element of the array. A pointer is a special type containing the address in memory of some data. You can use pointer arithmetic to access the elements in the array.

printf("Array address: %p\n", array);
for (int i = 0; i < 10; i++) {
    printf("Array element at address %p: %i\n", array + i, *(array + i));
}

Here the expression array + i gives the address of the ith element in the array. The expression *(array + i) retrieves the integer value stored at that address.

Here's a quick summary of C's basic pointer operations (we'll go into more depth in class). &E evaluates to the address of an expression E. *p gives the value stored at the location pointed to by p. p must have a pointer type, indicated by a * in the type declaration. For example, int * is the type representing a pointer to an int. So if p has type T *, then *p has type T.

As an example, read, compile, and run the program pointers.c. Try drawing the execution of the program on paper, with boxes for memory locations and arrows for pointers. Can you see how we end up with the final values for a and b?

Now it should make more sense why we pass arguments to scanf using the & operator.

EXERCISE 7: Write a program stack.c that illustrates whether the the program stack is growing "up" or "down." In other words, are new stack frames allocated at higher or lower addresses than old stack frames? You should NOT assume that variables in a single function are allocated in the order they're declared. You can define a new function much like you would define a new method in Java; for example, here is a function that takes a string and returns nothing:

void foo(char *s) {
    ...
}

Use git to add, commit, and push to GitHub when complete.

Structs

C does not have classes or objects. However, you'll often run into situations where you want to group related values together. For this purpose, you can create a struct, a special kind of user-defined type.

Structs are defined using the keyword struct, a name for the struct, and a list of member variables within curly braces. For example, here's a struct to represent a student (see student.c for the full code listing):

struct Student {
    char *first_name;
    char *last_name;
    int id;
};

You can create an instance of a struct type by declaring it with the struct keyword, and access member variables using the dot (.) operator.

struct Student s1;
s1.first_name = "Bart";
s1.last_name = "Simpson";
s1.id = 666;
printf("%d: %s %s\n", s1.id, s1.first_name, s1.last_name);

EXERCISE 8: Create a new file complex.c. In this file, add a struct type struct Complex containing two doubles representing the real and imaginary parts of a complex number. Use git to add, commit, and push to GitHub when complete.

EXERCISE 9: Add a function multiply_complex to complex.c that takes two complex numbers and returns a new complex number representing their product. Note that if you have two complex numbers c1 and c2 whose real parts are a1 and a2 and whose imaginary parts are b1 and b2, respectively, then the real part of their product is a1 * a2 - b1 * b2, and their imaginary part is a1 * b2 + a2 * b1. Include code testing your multiplication function in main. Use git to commit and push to GitHub when complete.

As you did at the end of Exercise 4, again create a tag to indicate the commit that we should grade. Use a tag of secondhalf; look back after Exercise 4 if you need a reminder on how to tag a commit. Make sure to push the update properly (remember, a normal git push doesn't push tags), and check on GitHub to make sure that it received your updates.

Turning in your work

Instead of using Moodle, we're using GitHub as the submission mechanism. Check again in GitHub to verify that the graders and I have access to your repository.

If appropriate, please also include a file credits.txt listing anyone other than me who helped you with the assignment, and any websites, books or other sources you used for reference. Please try to be thorough; in particular, if you had more than a passing interaction with another student, list them.


Written mostly by Laura Effinger-Dean, with help from Kochan's Programming in C and Java labs written by Dave Musicant and Andy Exley. Modified most recently, mostly with git updates, by Dave Musicant.