Section 4: C Debugging, Testing, Formatting
In the previous discussion section, you learned how to use C build and test frameworks to help automate the process of compiling and verifying your programs. In this discussion, we will continue to learn about new tools that can help us better debug, test, and format our programs.
1. Getting the Code
Once again, we will be using VS Code to log into the ecelinux servers:
- Start VS Code
- Use View > Command Palette to execute Remote-SSH: Connect Current Window to Host...
- Enter
netid@ecelinux.ece.cornell.edu
- Use View > Explorer to open folder on
ecelinux
- Use View > Terminal to open terminal on
ecelinux
First, fork the repositiory to your own GitHub profile: https://github.com/cornell-ece2400/ece2400-sec02-2025/fork
Uncheck the box Copy the main branch only. We want all the branches!
Now clone your forked version of the repo using the following commands (replace your-github
):
$ git clone --branch sec04 git@github.com:your-github/ece2400-sec02-2025 ece2400-sec04
$ cd ece2400-sec04
$ tree
The directory includes the following files:
avg-main.c
: source and main for single-fileavg
programsquare.h
: header file for thesquare
functionsquare.c
: source file for thesquare
functionsquare-adhoc.c
: test driver forsquare
function
2. Using GDB for Debugging
There are two kinds of C/C++ programmers in the world: printf debuggers and GDB debuggers. Students should use whichever debugging techniques make them the most productive coders. However, students should also not underestimate the power of attaching a debugger to your program.
Let's start by compiling the single-file program that to test our ubiquitous avg
function:
$ gcc -Wall -g -O0 -o avg-main src/avg-main.c
$ ./avg-main
Notice how we include the -g -O0
options for debugging.
This code has a bug and should give the wrong output. Let's start by using printf
debugging. Add some extra printfs to observe the state of the program as
it executes.
int avg(int x, int y) {
printf("x = %d, y = %d\n", x, y);
int sum = x - y;
printf("sum = %d\n", sum);
return sum / 2;
}
You should be able to see that the value for the sum
variable is
incorrect, but the value for the x
and y
variables are correct. This
means we can narrow our focus to line 3 in the above code snippet.
Hopefully, you should be able to spot the bug. Fix the bug, recompile,
and rerun the program.
Let's now try tracing the execution of this program using GDB. First, remove the extra printfs, undo your bug fix, and then recompile. Then you can start GDB like this
$ gdb -tui avg-main
GDB will drop you into a GDB "prompt" which you can use to interactively execute your program. Your source code will show up at the top, and the GDB prompt is at the bottom. Here are some useful GDB commands:
break location
: set a breakpointrun
: start running the programrecord
: start recording the execution for reverse debuggingstep
: execute the next C statement, step into a function callnext
: execute the next C statement, do not step into a function callrs
: reserve step, undo the execution of current C statementprint var
: print a C variablecontinue
: continue on to the next breakpointquit
: exit GDBrefresh
: refresh the source code display
GDB is very sophisticated so of course there are many more commands you can use, but these are enough to get started. Let's start by just running the program in GDB:
(gdb) run
Now let's try again, but first let's set a breakpoint to tell GDB to stop
at a certain function or line in the program. The following will set a
breakpoint at the beginning of the main
function.
(gdb) break main
You can see a little b+
marker in the margin next to the first
statement in the main
function indicating the location of the
breakpoint. You might need to use refresh
to get GDB to refresh the
source code display. We can now use run
to start the program running.
The execution should stop at the beginning of the function main
. You
should see the first line of the function highlighted.
(gdb) refresh
(gdb) run > temp
We are using > temp
so that any output from printf goes to a file and
does not mess up the source code display. We can use record
to save the computer's state after every instruction.
Then, we can step through the
execution of each C statement using the step
command.
(gdb) record
(gdb) step
Keep using step
until you get into the avg
function You can print out
the value of any variable using the print
command:
(gdb) print x
(gdb) print y
(gdb) print sum
You can also step backwards using the rs
command:
(gdb) rs
Try stepping forward and backwards through the avg
function and print
out various variables to see how they change during the execution. You
can use quit
to exit.
(gdb) quit
Now fix the bug and rerun the test.
3. Using clang-format
for Autoformatting
The course coding conventions are located here:
Following these conventions and indeed any coding conventions can be
quite tedious. Many software companies and open-source software projects
use code formatters to automate the process of formatting their code
to the style guide. One such tool is called clang-format
. This
tool takes a style file that specifies the coding conventions and then
tries to reformat your code so it adheres to the style file. We have
provided you a style file named .clang-format
that adheres to the
course coding conventions. You can run clang-format
like this;
$ clang-format -i src/avg-main.c
clang-format
will output the autoformatted source code. To really see
this in action though we need to write some poorly formatted code! Modify
the avg
function in avg-main.c
to look like this:
int avg( int x, int y) {
int sum = x + y;
return sum/2; }
The space before/after parenthesis is not consistent, and the curly braces are
on the wrong lines. Run clang-format
again like this:
$ clang-format src/avg-main.c
Verify that the code is beautiful again. Note that we are not actually
modifying the code, just outputting the autoformatted code to the console.
If you cat
the source code it still is not formatted correctly:
$ cat src/avg-main.c
You can use the -i
command line option to autoformat the code
"in-place"
$ clang-format -i src/avg-main.c
With autoformatting, students no longer have any excuse for poorly
formatted code! However, do keep in mind that the autoformatter does not
fix everything. This is where clangd and .gitignore
comes in.
Those two tools can help you with your variables names and prevent you from committing build files.
Students will still need to ensure:
- no irrelevant instructor supplied comments are included
- an appropriate amount of comments are included (not too little, not too much!)
4. Using GitHub Actions for Continuous Integration
What is the point of test cases and formatting standards if people can push sloppy code to your GitHub Repo anyways? Continuous integration is the process of continually integrating your code changes with your tests. It enfornces some level of code quality, before it is committed to your main repository branch. We will be using GitHub Actions to facilitate continuous integration. GitHub Actions will automatically run all tests for a project every time code is pushed to GitHub.
To start, you need to enable GitHub Actions with a workflow file. We have already created one of those files for you, and you can see it here:
$ cat .github/workflows/actions.yml
Although it is not important to understand the details of this YAML file,
you can still see that it includes steps to first compile
avg-main.c
, run it, and check the output is 15
.
Go ahead and commit all of the work you have done in this tutorial. Then, push your local commits to your forked repository on GitHub. As a reminder these are the appropriate git commands:
$ git add -u
$ git commit -m "fix sec04 code"
$ git push
$ git checkout main
$ git pull
$ git merge sec04
$ git push
Revisit the GitHub Actions page for this repository.
https://github.com/your-github/ece2400-sec02-2025/actions
Click on the current commit "working on sec04", and then
watch as GitHub Actions brings up a virtual machine and runs all the
steps to compile and test your code. If you click on "build" job, you can see the specific
failure or pass message. If you still need to, fix your code on ecelinux
, commit and
push your fix, and verify your code is now passing the tests on GitHub
Actions.
Note, you should always test your code directly on ecelinux
first. Do
not use GitHub Actions as the primary way to run your tests. GitHub
Actions is only for continuous integration testing. In this economy,
you can very quickly run out of Actions minutes.
5. To-Do On Your Own
If you have time, try compiling src/square-adhoc.c
in the repository
and executing the resulting binary. You make need to use CMake. Use GDB
debugging to find the bug and fix it.
After fixing the bug, add to the GitHub Actions workflow file with
correctness checks for src/square-adhoc.c
.
Finally, update the .github/workflows/actions.yml
file to check that
any committed code is properly formatted. Maybe you could even use a GitHub
Action from the Actions Marketplace.