Lab sessions Mon Jan 19 to Thu Jan 22
Lab written by Julie Zelenski
During this lab, you will:
First things first! Find an open computer to share with a partner. Introduce yourself and tell them about your favorite music to listen to while coding.
Get started. Make a clone of the lab starter project using the command
hg clone /afs/ir/class/cs107/repos/lab2/shared lab2
This creates the lab2 directory which contains source files and a Makefile. Some of you have asked about how you can set permissions so that both you and your partner can access the working directory. The
fs setacl command is used to add/remove permissions. Some examples are shown below, replace
username with the directory and sunet you wish to change.
fs setacl dirname username rl fs setacl dirname username rlidwka fs listacl dirname
The first two commands change the permissions on
dirname to give the user
username read and list permissions (rl) or full permissions (rlidwka). The
fs listacl command shows the directory's current permissions. You can use the variant
fsr setacl to recursively apply permissions to all subdirectories as well. Note: on myth as of Oct 2014, fsr has to be invoked it via its full path /afs/cs/software/bin/fsr.
Pull up the online lab checkoff and have it open in a browser so you'll be able to jot down things as you go.
Arrays and pointers. The file
arrptr.c contains a nonsense C program that uses arrays and pointers. Scan the source before compiling it. Start the program under gdb and set a breakpoint at
main. Run the program. When you hit the breakpoint, step through the first few lines which initialize the variables before the call to the
binky function and stop here to take a look around. First try out the gdb
x command which is used to examine raw memory :
(gdb) x/10wd arr
x dumps the contents of memory starting at a given address. In the command above, the modifiers
/10wd tell gdb to print 10 words (each word is 4 bytes) interpreting each word as a decimal integer. Use
help x to read about other available modifiers (btw, help is available for all gdb commands). Try some of the other options on arr to see the change in results.
Another key command to have in your gdb repertoire is
arr. First try to figure out what the result of the expression should be, then use
p) in gdb to confirm that your understanding is correct.
(gdb) p arr (gdb) p *arr (gdb) p sizeof(arr) (gdb) p arr (gdb) p arr = -99 (gdb) p *(arr + 1) (gdb) p &arr[-1] (gdb) p &arr - &arr (gdb) p &arr (gdb) p &arr
Notice that main initializes
arr, so it points to the same stack array. If you repeat the above expressions with
ptr substituted for
arr, most (but not) have the same result. Which ones are different? Why are they different? Resume execution from here using the gdb
step command to single-step into the call to
binky. The main function calls
ptr into the two arguments
binky, try printing the above expressions on parameters
b. Can you find any expression for which the two parameters differ?
Passing pointers by reference. One often-misunderstood aspect of C arrays/pointers is knowing when and why you need to pass a pointer itself by reference and deal with the dreaded double
**. Consider the functions
chop_to_back in arrptr.c. First manually trace the code and predict what effect each call will have on the variables in main. Then run under gdb and single-step through the calls using gdb print or x to observe what is happening in memory as you go. How is
chop_to_front successful in making a persistent change? Why is
chop_to_back not successful?
Once you understand the fatal flaw in
chop_to_back, change the function to enable the persistent change it is attempting. Because C has no pass-by-reference mechanism, you must manually add a level of indirection. Your new version of
chop_to_back can change
bufptr, but not
buffer. Understanding the difference is tricky! Stop and reason it through. Do you see why
buffer is not an L-value and how you cannot reassign where it points? (You may find it surprising that the expression
&buffer is even legal, but the
& is practically a no-op in this case-- compare what is printed for
Memory errors and valgrind. Valgrind is a supremely helpful tool for tracking down memory errors. However, it takes some practice to learn how to interpret a Valgrind report, so that's what this exercise is about. If you haven't already, review the cs107 guide to valgrind written by legendary CS107 TA Nate.
buggy.c programs contains a set of memory errors, a few of which get compiler warnings, but most compile without a care. The buggy program is designed to be invoked with a command-line argument (a number from 1 to 9) that identifies which error to make. For each numbered error N, first peruse the code in buggy.c to see what the error is and then try to predict the consequence of that error. Run buggy N without Valgrind and see what (if any) symptoms appear during normal execution. Then run buggy N again under Valgrind and see what it detects. Read the Valgrind report and see how it identifies the type of error, how many bytes were involved, the size and kind of memory at fault (stack/heap/global), and the line of code when the error was detected. How could you use these facts from a Valgrind report to find and fix the root cause of the error?
Becoming a skilled user of Valgrind is an invaluable asset when working on your assignments. We recommend that you run Valgrind early and often during your development cycle. Memory errors can be messy to de-tangle due to interference with one another, thus it's imperative to focus on resolving them one at a time. Your strategy should go something like this: run all newly-introduced code under valgrind, stop at the first error reported, study the report, follow the details to suspicious part of the code, ferret out root cause, resolve the problem, recompile, and re-test to see that this error has gone away. Repeat for any remaining errors. Although Valgrind is also handy for finding leaks, leaks don't demand the immediate attention that errors do. Leaks can (and should) be safely ignored until the final phase of polishing a working program.
Function pointers. The
numbers function in
fnptr.c creates an array of random numbers and uses the
qsort library function to sort the array into increasing order. Change the code so that array is sorted instead in decreasing order. The
strings function reads strings from a file and prints them out. Add code to unique the strings, so that each string is only entered into the array once. Read the man page for
lsearch for library functions appropriate for searching an unordered array.
If you finish with time remaining, we recommend doing the Assignment 2 fill-in-the-blank warmup exercise and work through any issues before starting on the assignment itself!
Before you leave, complete your checkoff form and ask your lab TA to approve it so you are properly credited. If you don't complete all the exercises during the lab period, we strongly encourage you to followup and finish the remainder on your own.