Miscellaneous C Topics

This lab is a collection of miscellaneous C topics and exercises that you may find useful as you build proficiency with the C language.

Debugging

"Debugging" is the process of methodically detecting and removing software defects. In this section we will discuss GDB (the GNU debugger), a tool that can help you debug your programs.

Basic GDB

To launch a debugging session, run the gdb command and tell it which program you want to debug. You may need to include the ./ as part of the executable name. For example:

gdb ./main

This will start an interactive debugging session, where you can issue commands to the debugger and see responses. To begin running the program, just type the run command (can be abbreviated r) and press ENTER. If no errors are encountered, you will see the program execute as usual, with a message at the end to indicate that the program did not crash:

[Inferior 1 (process 5361) exited normally]

To exit GDB, use the quit command.

Segmentation faults and backtraces

The debugger is a great tool for identifying the cause of segmentation faults. Here is an example program that contains a software defect:

#include <stdio.h>
#include <stdlib.h>

void foo(float *num)
{
    printf("My number: %f\n", *num);
}

void bar(float **wat)
{
    free(*wat);
    *wat = NULL;
}

int main()
{
    float *pi = (float*)malloc(sizeof(float));
    *pi = 3.14;
    foo(pi);

    *pi = *pi / 2.0;
    foo(pi);

    bar(&pi);
    foo(pi);

    return 0;
}

If you run the above program, the program will crash with an error similar to the following:

Segmentation fault (core dumped)

Can you find the segfault in the program above? You can probably narrow down the problem by observing how many lines are printed (and therefore how many times the foo function is called), but it will probably take you a few minutes to figure out where the segfault is happening.

Running the program in GDB also produces a segfault, but now it tells us exactly where the segfault occurs:

$ gdb a.exe
GNU gdb (GDB) 7.8
[...]
(gdb) r
Starting program: /home/lam/tmp/a.out
[...]
My number: 3.140000
My number: 1.570000

Program received signal SIGSEGV, Segmentation fault.
0x00000001004010f0 in foo (num=0x0) at segfault.c:6
6           printf("My number: %f\n", *num);

That's a LOT more information, and now we have a concrete place to start debugging (line 6 of segfault.c). You can get even more information about how the program got to this point using the "backtrace" command (abbreviated "bt"):

(gdb) bt
#0  0x00000001004010f0 in foo (num=0x0) at segfault.c:6
#1  0x00000001004011c4 in main () at segfault.c:25

GDB is an extremely powerful tool with many other features that can help you debug your program faster. There are many excellent GDB tutorials online, and there will be a UUG tutorial at JMU in the coming weeks. If you are interested, we encourage you to explore!

Debugging on Mac OS X

If you are on Mac OS X 10.9 or later, you will need to use the LLVM debugger instead of GDB. Use the lldb command to launch the LLVM debugger. Here is a quick reference guide to the differences between GDB and the LLVM debugger.

Memory Analysis

In this section, we will discuss Valgrind/Memcheck, a tool that will help you find memory leaks. Here is a short quick-start guide.

Installing Valgrind

Valgrind is already installed in the labs and on stu. If you are using the supported Linux Mint VM, you can install it by running the following command from the terminal:

sudo apt-get install valgrind

Basic Memcheck output

Memcheck is the default mode for Valgrind, so all you need to do is execute valgrind and tell it which program to test. You may need to include the ./ as part of the executable name. For example:

valgrind --leak-check=yes ./main

This will run main with extra instrumentation attached to measure and track your memory usage. It will also attempt to warn you of any memory leaks. Example output (w/ no leaks):

==5300== Memcheck, a memory error detector
==5300== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==5300== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==5300== Command: ./main
==5300==
==5300==
==5300== HEAP SUMMARY:
==5300==     in use at exit: 1,080,000 bytes in 1 blocks
==5300==   total heap usage: 3 allocs, 2 frees, 1,082,368 bytes allocated
==5300==
==5300== LEAK SUMMARY:
==5300==    definitely lost: 0 bytes in 0 blocks
==5300==    indirectly lost: 0 bytes in 0 blocks
==5300==      possibly lost: 0 bytes in 0 blocks
==5300==    still reachable: 1,080,000 bytes in 1 blocks
==5300==         suppressed: 0 bytes in 0 blocks
==5300== Rerun with --leak-check=full to see details of leaked memory
==5300==
==5300== For counts of detected and suppressed errors, rerun with: -v
==5300== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

If you want even more detailed information about potential memory leaks, you can add the "--leak-check=yes` command-line option:

valgrind --leak-check=yes ./main

IMPORTANT: This will slow down your program considerably, so be prepared to wait a bit.

Extra Exercises

Exercise: Line numbers

DO THIS: Write a program that reads lines of text from standard input and adds line numbers at the beginning of each line, printing the results to standard output.

Example input:

This is
a

test file.

Example output:

1 This is
2 a
3
4 test file.

Use GDB to fix any issues you encounter and use Valgrind to ensure that your solution does not leak any memory.