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¶
Create a project directory:
mkdir -p ~/c-projects/hello-world cd ~/c-projects/hello-world
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; }
Build the source code using the
gcc
compiler, specifyinghello
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 howgcc
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.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.
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}
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.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.
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.
Compile the program using
make
:dev@ubuntu:~/projects/hello-world$
make
gcc -c hello.c -o hello.o
gcc -o hello hello.o
Checking the resulting files would show:
dev@ubuntu:~/projects/hello-world$
ls -1
hello
hello.c
hello.o
Makefile
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.
Recompile the program with the
-g
option, which produces debugging information specifically for GDB:gcc -g hello.c -o hello
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:
Line 6: the
hello
program is started.Line 15: the
number
variable is queried before being assigned.Line 17: the program execution is stepped forward.
Line 19: the
number
variable is queried again after being assigned.
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.Press Ctrl+D to quit the debugging session.