Develop with GCC on Ubuntu

This tutorial shows how to build and debug C programs on Ubuntu using GCC and GDB. For instructions on how to install GCC and related tooling, including IDEs, debuggers, and the build system, see the dedicated guide on How to set up a development environment for GCC on Ubuntu. This article assumes that tooling suggested in that article has been installed.

The GNU Compiler Collection (GCC) provides support for a number of programming languages. Working with the GCC toolchain in Ubuntu is straightforward, so this tutorial is limited to showing a basic ‘Hello, world!’ program using the C language and a basic debugging session.

Writing a sample C program

  1. Create a project directory:

    mkdir -p ~/c-projects/hello-world
    cd ~/c-projects/hello-world
    
  2. Write a ‘Hello, world!’ program that includes setting a variable. Create a hello.c file with the following content:

    hello.c
    #include <stdio.h>
    
    int main() {
        int number = 42;
        printf("Hello, world!\nThe answer to everything is %d.", number);
        return 0;
    }
    
  3. Build the source code using the gcc compiler, specifying hello as the file name of the resulting executable:

    gcc hello.c -o hello
    

    This command incorporates the intermediate steps of preprocessing, compiling, and linking to generate an executable in one go. Use the -v option to display detailed information on how gcc invokes the individual tools that handle the intermediate steps.

    Note

    The gcc command can be used to compile source code in many programming languages, including C, C++, and assembler. The compiler guesses the language used based on the file extension (for example, .c, .cpp, or .s). You can explicitly specify the language using the -x option.

  4. Run the program executable:

    dev@ubuntu:~/projects/hello-world$ ./hello
    Hello, world!The answer to everything is 42.

Fixing simple bugs

The gcc compiler provides helpful output when it encounters a problematic part of the source code.

  1. Modify the ‘Hello, world!’ program source to omit an expected argument. Note the missing number argument on line 5:

    hello.c
    1#include <stdio.h>
    2
    3int main() {
    4    int number = 42;
    5    printf("Hello, world!\nThe answer to everything is %d.");
    6    return 0;
    7}
    
  2. Build the source:

    $ gcc hello.c -o hello
    hello.c: In function ‘main’:
    hello.c:5:57: warning: format ‘%d’ expects a matching ‘int’ argument [-Wformat=]
        5 |     printf("Hello, world!\nThe answer to everything is %d.\n");
        |                                                        ~^
        |                                                         |
        |                                                         int

    gcc displays a warning, but it still compiles the source code.

  3. Run the program to see how it misbehaves without the integer argument specified:

    dev@ubuntu:~/projects/hello-world$ ./hello
    Hello, world!The answer to everything is -1350680728.

Using the Make build system

Compiling manually by invoking gcc directly can be useful to understand the build process or for simple projects like ‘Hello, world!’. For larger projects, use a build system to simplify and automate the process.

See below for a simple example of the use of the GNU Make build system to build the ‘Hello, world!’ program from Writing a sample C program.

  1. Create a basic Makefile (a file named Makefile in the project directory) with the following content:

    ## Variables:
    
    # Compiler
    CC = gcc
    
    # Target executable
    TARGET = hello
    
    # Source files
    SRCS = hello.c
    
    # Object files
    OBJS = $(SRCS:.c=.o)
    
    ## Targets:
    
    # Default target
    all: $(TARGET)
    
    # Linking
    $(TARGET): $(OBJS)
    	$(CC) -o $@ $^
    
    # Compiling
    %.o: %.c
    	$(CC) -c $< -o $@
    
    # Cleaning
    clean:
    	rm -f $(OBJS) $(TARGET)
    
    .PHONY: all clean
    

    Note that the Makefile divides the build process into separate compilation and linking steps. It also makes use of “automatic variables” that allow for writing the compilation rules without specifying absolute file names. For an overview, see Automatic Variables in the GNU Make manual.

    Important

    Makefile rules must start with the tab character. See Rule Syntax.

  2. Compile the program using make:

    dev@ubuntu:~/projects/hello-world$ make
    gcc -c hello.c -o hello.ogcc -o hello hello.o

    Checking the resulting files would show:

    dev@ubuntu:~/projects/hello-world$ ls -1
    hellohello.chello.oMakefile
  3. Run the program executable:

    dev@ubuntu:~/projects/hello-world$ ./hello
    Hello, world!The answer to everything is 42.

Debugging with GDB

GDB is the standard debugger accompanying GNU GCC. While you can use debugging capabilities of your preferred IDE, GDB offers adequate debugging support for the command line.

See the example below for a quick introduction to GDB use with the ‘Hello, world!’ program from Writing a sample C program.

  1. Recompile the program with the -g option, which produces debugging information specifically for GDB:

    gcc -g hello.c -o hello
    
  2. Run the debugger:

     1$ gdb hello
     2
     3#[snip]
     4
     5Reading symbols from hello...
     6(gdb) start
     7Temporary breakpoint 1 at 0x1155: file hello.c, line 4.
     8Starting program: /home/rkratky/c-projects/hello-world/hello 
     9
    10[Thread debugging using libthread_db enabled]
    11Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
    12
    13Temporary breakpoint 1, main () at hello.c:4
    144     int number = 42;
    15(gdb) print number
    16$1 = 32767
    17(gdb) next
    185     printf("Hello, world!\nThe answer to everything is %d.\n", number);
    19(gdb) print number
    20$2 = 42
    21(gdb)
    

    In the above example:

    1. Line 6: the hello program is started.

    2. Line 15: the number variable is queried before being assigned.

    3. Line 17: the program execution is stepped forward.

    4. Line 19: the number variable is queried again after being assigned.

  3. Press Ctrl+X+A to switch to the text user interface (TUI) of GDB for a more interactive experience:

    ┌─hello.c───────────────────────────────────────────────────────────┐
           1 #include <stdio.h>                                       
           2                                                          
           3 int main() {                                             
           4     int number = 42;                                     
     >     5     printf("Hello, world!\nThe answer to everything is %d
           6     return 0;                                            
           7 }                                                        
           8                                                          
                                                                      
                                                                      
                                                                      
    └───────────────────────────────────────────────────────────────────┘
    Thread 0x7ffff7f897 (src) In: main          L5    PC: 0x55555555515c  
    (gdb) print number
    $1 = 32767
    (gdb) next
    (gdb) print number
    $2 = 42
    (gdb)

    Type help at any point to see available commands.

  4. Press Ctrl+D to quit the debugging session.