Main navigation | Main content
To introduce this lab, we will have you deal with structs, linked data structures, malloc, and how to use valgrind and gdb, two useful tools to help you debug programs. As a reminder, labs are solely attendance based, if you are here, work on the lab and then get checked off by a TA, you are fine. However, that does not mean you shouldn’t try to finish these labs, even if it is outside of class. If you need help, you are welcome to go to office hours, which are listed on the website. The problems presented in lab are meant to give you hands-on practice with the topics covered in lecture and that will appear on assignments.
For question 1, you will be looking at creating a simple
bank_Account
struct and then implementing it in a simple
main
function. Use this command to get the template.
cp /web/classes/Fall-2018/csci2021-010/labs/0x2/bank_template.c .
For the struct you will need four fields: First name (char[]), Last name (char[]), account number (long), and balance (double).
Next you are asked to implement the main
function. The
sections you need to complete are marked within the template. The
tasks are to create a new struct instance then get a first name and
last name from the user to instantiate those fields. Secondly, to
instantiate the other fields, pick an account number of your choosing.
The next task is to deposit then withdraw amounts determined from the
user. To deposit, a simple printf
and scanf
will be needed to get the user’s deposit, then the correct data field
of the struct will need to be updated. Make sure the user enters the
value with a decimal value. This could be specified by asking for the
cents of the value too. The withdrawal will be a bit more difficult
because it will be important that there is no overdraw, or that the
balance does not go below 0. To do so, within a while loop, use
printf
and scanf
to get information from the
user, the same need for a decimal value applies here as well. Then
check their withdrawal and if it is not greater than their current
balance, update the correct field, and set success
to 1.
Otherwise print an error message of your choosing and let the loop run
again.
printf
and scanf
)
For question 2 you are going to complete a program that requires
some scanf
and printf
statements. We will
also ask you to write a bit of code regarding binary representations.
To get the code template:
cp /web/classes/Fall-2018/csci2021-010/labs/0x2/binary_template.c .
You will be printing and scanning in values other than %d in
this case. In fact, you will need to print and scan for hexadecimal
and octal representations of integers. Every place a
scanf
or printf
is needed will be
marked on the template. It is your choice as to the messages you give
with your printf
statements.
Once you get all the printf
and scanf
statements complete, run the program to see if you know your
hexadecimal and octal representations well enough. (Note: for input of
your guesses, you DO NOT need to use the prefixes, i.e. ‘0x’
for hexadecimal and ‘0’ for octal.)
Something you might have noticed is that we did not
ask for a binary representation. This is because printf
and scanf
do not have a specifier for binary. In this
case, write some code that will print the binary representation of an
integer. A more in depth description is given in the template.
For question 3, you will be debugging the Fibonacci program from last week that has had a bunch of mallocs added. This is to help you see some of the mistakes you may come across when trying to use malloc. To get the file:
cp /web/classes/Fall-2018/csci2021-010/labs/0x2/fibMalloc_buggy.c .
The program does not compile or run as it should as the mallocs, frees, etc. are not all used correctly. It is your job to fix them so that the program runs smoothly.
First, we ask that you get the file to compile. Make sure you add the flags -Wall and -g when doing so. This will allow you to use gdb and valgrind in the next step as well as give all possible warnings when compiling. Both of which will help you.
After the program compiles, try running it. You should see that it is not doing what it should and may even seg fault. You could just stare at the code but using gdb and valgrind will help you find the problems a bit easier. First, for valgrind, run the following line in the terminal:
valgrind ./executable
This should run your program under valgrind which looks at your program’s memory usage. Look to see what kind of messages are brought up, what information it gives, etc. Some basics are error messages, memory used, and the number of frees and mallocs. Based on this, you should be able to fix all the issues.
As a warning, there may be a message of an invalid free error. This could be due to multiple frees or freeing invalid memory. You can look at the rest of the message to possibly trace where this occurs.
A resource for using valgrind is this tutorial. There's also the official website.
If you want to try another way to debug a program, you can use gdb. Firstly, make sure you compiled your file with -g. This makes sure there are debugging flags on your program. Next, to run gdb use the command
gdb -tui ./executable
You can use gdb without the -tui, but -tui, the text user interface, allows you to look at the relevant code you are working on. You can look at both and see which you prefer.
Next, to run the program, type run
. You’ll see that the
screen gets a little messed up when you run certain commands. To fix
it, press Control-L.
To start the debugging, use this command:
start
This allows you to run the program and stop at main
.
The command start replaces setting a breakpoint at main and
using the run command. If you want to stop at other
functions, you can set a breakpoint with this command:
break function_name
NOTE: If you ever go into a library function, such as
printf, you an use the command finish
to get
out.
After the program has stopped, to keep going, you can either say
continue and run the rest of the program, or next
and it will move one line in your code. Once you get past the
initializations, or to the printf
, type the command:
print i
You should see that it showed you the value of i without
having to add a printf
in your program. Whenever you
are done with gdb, just use q or quit to
exit.
Now that you know the basics, a helpful guide to gdb is this tutorial. The complete manual can help you with finding other commands you can use in gdb to help you debug.
For the last question, you will be completing a program that uses a binary search tree to represent an encrypted message that you will need to decode. To get the file:
cp /web/classes/Fall-2018/csci2021-010/labs/0x2/tree_template.c .
Your first step is to look at the struct and make sure you understand what it contains and how that allows you to represent a binary search tree in C. If you've studied binary search trees in the past, it shouldn't be too difficult to see. If you have any questions, ask a TA.
Your second step is to implement an add_tree
function
for the tree. If you remember, a binary search tree’s nodes are in sorted
order, those to the left are less than the ones to the right. In
ours, the value the nodes are sorted by are their encrypted
character, encrypted_char
. You can implement this as
you wish, but as you’ll see with some of the completed functions,
recursion is very helpful when it comes to making functions for
binary trees. To test this function just comment out the last 5
lines of main excluding the for loop. Just remember to uncomment
them before testing the etire program.
After that is implemented, you are also asked to complete the
decode_message
function. For this function, you will
have an array of binary trees each of which contains an encrypted
character (as an int), an index to its place in the final
message, and an index to its mask in the decoder. The other
arguments are the decoder, the size of the tree, and the array you
will put the new characters in. The decoder (given in
main
) contains a list of integer masks. These are just
sets of specific binary numbers that you can use with other values
and a binary operator to then get a new value. In our case the mask
will be paired with the encrypted character to get the real
character for the message. Your hint as to which binary operator you
should use for this is that if two bits are the same, it becomes a
0, if they are different, they become a 1. Each decoded character is
then placed in a char *, which if you remember, can be used
like an array.
Make sure the program runs correctly and gives a sensible message, then run valgrind to make sure there are no memory issues.
Lastly, after you’ve gotten the entire program running and get a sensible message, look at the main function to see how all the trees are freed at the end. This is especially important because they were malloc’d outside of main within a separate function.