University of Minnesota
Machine Architecture and Organization (sec 010)
index.php

Csci 2021 Lab 0x2

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.

  1. (Simple Structs)

    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.

  2. (Binary representations in 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.

  3. (Fix the malloc with valgrind and gdb)

    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.

  4. (Binary search trees and a secret binary message)

    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.