Lab Session - Week 2

C and gdb

Get example programs

  $ cd ~/cs31/inclass
  $ cp -r ~mauskop/public/cs31/week02/ .
  $ ls
  arrays.c   Makefile    mauskop.txt    readfile.h 
  readfile.c testprog.c  studentinfo.c

Review: compiling and running a C program

To compile and run testprog.c:

  $ gcc -g -o testprog testprog.c
  $ ./testprog
  x = 7  y = 8
  x = 58 y = 8
  

The Makefile provided lets you compile more easily with the make command. Running make clean deletes the files produced by make. After you save changes to a program, you must rerun make to see those changes reflected when you test the program.

More information on gcc is available here.

Statically Declared Arrays

The program arrays.c contains an example of a statically declared array. Arrays always occupy a contiguous chunk of memory. A statically declared array occupies a contiguous chunk in the stack frame of the function that declared it. The C compiler needs to know how much space in memory to reserve for a function's stack frame; this means that the size of an array stored on the stack must be known before the program starts to run.

C doesn't keep track of how big its arrays are; the programmer must do this bookkeeping to make sure they don't read or write beyond the boundaries of an array. C arrays are altogether less convenient than Python lists, but they are more efficient.

readfile Library

Your lab 2 code will make use of a library called readfile. To use this library, you first call open_file with the name of a file. Then you call functions, like read_int or read_float, that read in values of various types from the file you passed to open_file. This function will fail if the next value in the file can't be read as an int or if there is nothing more to read in the file. It returns -1 when it fails and 0 when it succeeds. But if it's returning this status code, it can't also return the value that was read in. Moreover, C always passes arguments to functions by value. This means the called function (callee) gets a copy of the value and can't modify the original value in the calling function (caller). So we can't pass in an int and have read_int set it to the value read from the file.

The workaround here is to pass not an int, but a memory address where an int can be stored, also known as a pointer to an int. If we put the "address of" operator, &, immediately before a variable, we get the memory address of that variable. If we pass an address to a function, like read_int, it can set the value at that address, in effect returning an additional value to its caller. You can see an example of this in studentinfo.c.

You may notice that the call to read_string doesn't make use of the & operator. This is because a C string is an array of characters, and when we refer to an array without indexing it, we get back the address where that array begins.

GDB intro

gdb is the gnu debugger for C and C++ programs. Last week we used gdb as a calculator and converter, but it is normally used to debug programs. To use the debugger, first compile your C program with the -g flag. Next, run the executable file inside gdb.

$ gdb ./testprog

The first thing we get is the gdb prompt (our program has not yet started). Typically we will set a breakpoint at main. A breakpoint tells gdb to grab control at a certain point in the execution, in this case right before the first instruction in main is executed.

(gdb) break main

Next, we enter the run command at the gdb prompt and gdb starts running our program.

(gdb) run

Whenever your program reaches a breakpoint, gdb will pause so you can enter more commands.

(gdb) next    # this will execute the instruction x = 10

The list command lists the C source code around the point where we are in the execution.

(gdb) list

list with a line number lists the source code around that line.

(gdb) list 30

cont tells gdb to let the program continue running. Since we have no more breakpoints it will run until termination. Now let's add a breakpoint in the function mystery, and rerun the program.

(gdb) break mystery
(gdb) run

Let's set a breakpoint at line 20, right before the call to mystery, then type cont to continue execution from the breakpoint in main.

(gdb) break 20
(gdb) cont
(gdb) list

We can use the print command to print out expressions in the program, so let's cont to the breakpoint in mystery and then print out the values of its parameters.

(gdb) cont
(gdb) print a        # print out the value of the variable a
(gdb) print (a - 4)  # print out the value of the expression (a - 4)

When you are all done using gdb, type the command quit.

Github With a Partner

You and your partner should each maintain a local copy of your shared repository. Ideally, you take turns editing the files in the shared repo. Each time you begin working on the lab you should start by running git pull. This pulls in any changes your partner made since you last updated your local copy. When you finish, or whenever you make progress, you should add, commit, and git push your changes. Now your partner (and the instructors) can see the version of the repo that includes these changes. When your partner takes a turn editing the files, they should follow the same sequence of steps.

If you and your partner edit the files simultaneously, or if you forget to run git pull before you start editing, you might end up with a merge conflict the next time you try to push. This means that you made changes on top of a version of the repo that's now out of date. Sometimes github can figure out how to resolve the conflict in a way that incorporates all the changes you made and all the changes your partner made. When it can't, this is usually because you and your partner both edited the same part of some file. In this case you need to manually reconcile the changes you made with the changes your partner made. If you encounter a difficult merge conflict ask a ninja for help or consult the using git guide.

References

Professor Newhall has written some references on C and gdb: