Termbank
  1. A
    1. API
    2. ASAN
    3. Access specifiers
  2. C
    1. Constructor
    2. const
  3. G
    1. GDB
    2. g++
  4. M
    1. Make
    2. Memory Leak
  5. P
    1. Pointer
  6. R
    1. Recursion
  7. S
    1. std
  8. V
    1. Valgrind
Completed: / exercises

LAB_DEBUG: Introduction to Debugging

Due date: 2024-03-25 23:59.
Debugging is an essential skill for any programmer, and it involves finding and fixing errors or bugs in your code. In this exercise, you will be given a piece of C++ code that compiles and runs, but has some errors in it, and your task will be to identify and correct those errors.

Download The Provided Files

Just like you already did it in LAB_INTRO, start with downloading and unzipping the provided files from here: lab_debug.zip.
The code for this activity resides in the lab_debug directory. Get there by typing the following line in the terminal while in your working directory (you can press tab to autocomplete):
cd lab_debug
You should now have the following files in your lab_debug directory:

Determining What's Going Wrong

As you can see, the source code files we provided you with are almost all familiar now, except for sketchify.cpp. This is where we planted some bugs for you to catch.
To figure out what is wrong you could try opening sketchify.cpp and start looking through the code. This is a good approach for fixing logical bugs (such as when a wrong color gets applied to the output image than intended), however, it's usually not the best option for fixing runtime errors or general code bugs. In such cases, you should follow the workflow outlined below:

Debugging Workflow

Quoted from DEBUGGING: The 9 Indispensable Rules for Finding Even the Most Elusive Software and Hardware Problems, by David J. Agans.

Understand the System

Make it Fail

Quit Thinking and Look

Divide and Conquer

Change One Thing at a Time

Keep an Audit Trail

Check the Plug

Get a Fresh View

If you didn't fix it, it ain't fixed

Basic Instrumentation: Print (cout) Statements!

The easiest way to debug your code is to add print statements. To do this, you can add comments at various points in your code, such as:
cout << "line " << __LINE__ << ": x = " << x << endl;
__LINE__ is a special compiler macro containing the current line number of the file.
If you're getting compiler errors after trying to use cout statements, then you need to #include the iostream library like this:
#include <iostream>
using namespace std;
Additionally, by having the using namespace std; line at the top, you don't need to type std::cout every time (cout is a standard function that is included in the std namespace). Note that if you tried to use other functions that are named the same as standard functions, it would be ambiguous, which is why using qualified names (such as std::cout) is sometimes preferable.
The above line prints out the current line number as well as the value of the variable x when that line number executes, for example:
line 32: x = 3
Print statements work for debugging in (almost) any language and make repeated debug testing easy - to repeat debug testing with a new change, all you need to do is compile and run the program again. They also require nothing new to learn (smile) .

Compiling and Running the Code

To compile (make) and run your code, type the following into your terminal:
make
cp in_01.png in.png
./sketchify
Notice that if you do not copy your files with cp in_01.png in.png, the code will exit with an error message [EasyPNG]: Failed to open in.png. Make sure you copy your sample input files in_01.png into the expected input files in.png.

Debugging the Code: Bug 1

As you can see, the code crashed with Segmentation Fault (segfault). This happens when you access memory that doesn't belong to you - such as dereferencing a NULL or an uninitialized pointer.
You might also get munmap_chunk(): invalid pointer error which also indicates a problem with a pointer. Uninitialized pointers can have unpredictable results, so the error might vary in some cases...
Try adding print statements to lines 32 and 34, before and after the calls
to original->ReadFromFile(), width(), and height().
cout << "Reached line 32" << endl;
cout << "Reached line 34" << endl;
Now run sketchify again. You'll see line 32 print out, but not line 34. This means the segfault occurred sometime between executing lines 32 and 34. We'll let you figure out what the bug here is and how to fix it. You'll see line 34 print to the terminal once you've finished - then move on to Bug 2, below.

Bug 2

Once you've fixed the first bug, you'll get another segfault. You'll want to narrow down the line it's occurring on and its cause by printing more information. Try putting cout statements at the beginning and end of the inner for loop.

More debugging!

Like most code out there, the code we provided you with isn't perfect. However, fixing it can be as simple as repeating the steps outlined above to gain a better understanding of what the program is doing at runtime, thus enabling you to fix the bugs. Good luck!

Testing Correctness of Your Code

Once you think sketchify is working, you can compare your output (out.png) to the expected output using diff:
diff out.png out_01.png
If they match (diff won't give any output if they do), you've finished. If not, you've still got a bit more debugging to do - go back and add some print statements to figure out why the outputs differ.
If diff is unhelpful, you can open each image up using a graphical viewer. If you need more advanced tools, for example in Paint.NET, you can open the images in separate layers and change blending to 'difference', but most likely just having the images layered and swapping between them is enough to reveal differences in the image content.

Additional Resources

Pointers refresher: Binky Pointer.

Submitting Your Work

Your task is to fix the errors in sketchify.cpp such that it compiles and runs without errors and produces out.png that matches out_01.png.
Note that the input file should be called in.png.

Allowed filenames: sketchify.cpp

Warning: You have not logged in. You cannot answer.

Acknowledgments

We would like to express our gratitude to prof. Cinda Heeren and the student staff of the UIUC CS Data Structures course for creating and sharing the programming exercise materials that have served as the basis for the labs used in this course.
Revised by: Elmeri Uotila and Anna LaValle
?
API stands for Application Programming Interface. In the context of this course, you can consider the header files to be such interfaces, as they determine what class functions and properties are public and thus accessible from anywhere.
AddressSanitizer (ASAN) is a memory error detector for C/C++. In this course, the makefiles will typically compile an executable that uses ASAN, with "-asan" at the end of its name.
The two notable access specifiers are:
  • public: class members defined after the public keyword are accessible from outside the class.
  • private: class members are generally private by default and thus not accessible from the outside
Constructor is a special non-static member function of a class that is used to initialize objects of its class type. A constructor is called upon initialization of an object. A constructor without arguments is a default constructor, but constructors that take arguments can be defined.
GDB, the GNU Project debugger, allows you to see what is going on `inside' another program while it executes -- or what another program was doing at the moment it crashed.
GDB can do four main kinds of things (plus other things in support of these) to help you catch bugs in the act:
  • Start your program, specifying anything that might affect its behavior.
  • Make your program stop on specified conditions.
  • Examine what has happened, when your program has stopped.
  • Change things in your program, so you can experiment with correcting the effects of one bug and go on to learn about another.
GNU Make is a tool which controls the generation of executables and other non-source files of a program from the program's source files. Make gets its knowledge of how to build your program from a file called the makefile, which lists each of the non-source files and how to compute it from other files. When you write a program, you should write a makefile for it, so that it is possible to use Make to build and install the program.
Memory leak means that the program is not freeing up memory it reserves. The memory will be freed when the program terminates, but if a program keeps leaking more and more memory without terminating, it can become a huge issue!
A typical way for memory leaks to occur is reserving memory with new and not calling delete before the pointer goes out of scope.
Pointer variables store a memory address as their value. In other words, they point to some data. The data can be accessed by dereferencing the pointer. (Either like *p or p->...)
A recursive function calls itself from within it. The recursion call must be conditional or it would lead to an infinite loop.
Valgrind is another tool besides ASAN that you can use in this course. It can detect many memory-related errors that are common in C and C++ programs and that can lead to crashes and unpredictable behaviour.
const is a keyword meant to mark something as immutable
  • A const object cannot be modified: attempt to do so directly is a compile-time error, and attempt to do so indirectly (e.g., by modifying the const object through a reference or pointer to non-const type) results in undefined behavior.
  • const keyword on an object's member function prevents the object from being modified in the function
  • Pointer declarations can have 2 const keywords, one to make the data that's pointed at unable to be modified and one to make the pointer itself unable to be modified
Using const improves code readability and prevents accidental modification of objects.
g++ is a C++ compiler that we primarily use for this course. It is the C++ compiler for the GNU compiler collection. You may sometimes see gcc being used instead of g++, which was originally the GNU C compiler, but calling gcc generally also compiles .cpp files as C++. However, calling g++ is preferred.
In C++, std stands for Standard Library, which is a collection of commonly useful classes and functions. Typically, these are defined in the std namespace.