Compiling software from C/C++ source code allows creating tailored programs and trying out latest versions before packaged binaries are released. The build-essential packages provide the necessary compilation tools for this on Ubuntu and Debian systems.
In this comprehensive 2600+ word guide, we will cover everything about build essentials—what‘s included, how to install, best practices for compiling code, troubleshooting issues and much more from an expert developer‘s perspective.
Overview of Build Essentials Packages
The build-essential metapackage brings together multiple packages required for compiling software from C/C++ source code on Ubuntu/Debian:
GNU Compiler Collection (GCC)
GCC is a widely used compiler for C and C++ code. It converts source code into machine readable binary instructions for the CPU. Some key points about GCC:
- Supported Languages: C, C++, Java, Ada, Go, D and more
- Active development for over 30 years
- Used by many open source projects like the Linux kernel
- Compiles to optimized native machine code for fast performance
- Supports cross-platform compilation for different architectures
- Extensive warning messages help write secure code
- Active community behind it for support
The default version installed by build-essential on Ubuntu 18.04 is GCC 7.x. Newer Ubuntu versions come with GCC 9.x.
G++
G++ is the GNU C++ compiler and is included within the GCC package itself. It provides all functionality for compiling C++ code. The g++ command functions identically to gcc with C++ enabled by default.
Make and Build Tools
Make is a utility for automating and managing the compilation process. Developers create Makefiles that list source files, directives, targets and commands required to build executables from code.
Other build tools like autoconf, automake, libtool that produce Makefiles are also included in the build-essential packages.
Debuggers and Profilers
GDB (GNU Debugger) is the standard debugger for debugging executable files created from C/C++ code. It allows stepping through code, breakpoints, memory inspection and more.
Valgrind is a instrumentation framework for dynamic analysis to debug issues like memory leaks. It marks uninitialized memory usage, illegal frees etc.
Binary Utilities
Tools like ar for creating static libraries, nm for listing symbols from object files and ranlib to generate indexes for archives are provided.
Linux Headers and Man Pages
Headers contains prototypes and definitions for APIs provided by the Linux kernel and glibc. This allows interfacing with core OS functionality.
Format specifiers for GNU compilers and Make are included in the man pages for reference.
Step-by-Step Guide to Install Build Essentials
Build essential packages are available in the standard Ubuntu repositories. We will install them with the apt package manager:
1. Update Package Index
Run apt update to fetch metadata of latest package versions:
sudo apt update
2. Install build-essential Package
Use the following apt install command:
sudo apt install build-essential
This will install GCC, G++, make and other compilation tools discussed earlier.
Sample output:
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
autoconf automake autopoint binutils bzip2...
Suggested packages:
[...]
The following NEW packages will be installed:
autoconf automake autopoint binutils bzip2...
gcc-8 gcc-8-base libasan4... make
0 upgraded, 18 newly installed, 0 to remove and 14 not upgraded.
Need to get 32.1 MB of archives.
After this operation, 103 MB of additional disk space will be used.
[...]
Processing triggers for man-db (2.8.3-2ubuntu0.1) ...
Setting up gcc-8-base:amd64 (8.3.0-6ubuntu1~18.04.1) ...
Processing triggers for libc-bin (2.27-3ubuntu1) ...
This installs GCC 8.x on Ubuntu 18.04. The latest Ubuntu 20.04 comes with GCC 9.x by default.
3. Verify Installed Packages
To list all packages installed with build-essential, use:
dpkg --list | grep build-essential
Sample output:
ii build-essential 12.4ubuntu1 amd64 Informational list of build-essential packages
ii libasan4:amd64 7.5.0-3ubuntu1~18.04 amd64 AddressSanitizer -- a fast memory error detector
ii libatomic1:amd64 8.4.0-1ubuntu1~18.04 amd64 support library providing __atomic built-in functions
[...]
ii make 4.1-9.1ubuntu1 amd64 utility for directing compilation
Check gcc version with:
gcc --version
Sample output:
gcc (Ubuntu 8.3.0-6ubuntu1~18.04.1) 8.3.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
This confirms GCC 8.3.0 and tools were installed correctly.
Compiling a Test C Program
Let‘s create a simple C demo program to test compilation:
// test.c
#include <stdio.h>
int main() {
printf("Compilation works!\n");
return 0;
}
Save this as test.c and compile into an executable with:
gcc test.c -o test
Run the test program with:
./test
Output:
Compilation works!
This confirms our toolchain is correctly set up for compiling basic C programs.
Advanced Makefiles and Compilation Topics
While small programs can be compiled easily with gcc source.c, large projects require Makefiles to manage builds.
Key Features of Makefiles
- Specify compiler flags like -Wall for warnings
- Pass include paths and library linking flags
- Define macros for reusable flags
- List source, header files as dependencies
- Support conditional logic, functions
- Built-in rules for compiling common source types
- Recursively descend into subdirectories
- Incremental builds only recompile changed parts
Here is an example Makefile:
CC = gcc
FLAGS = -Wall -Wextra -pedantic
INCLUDE = -I/usr/include/libpng
build: main.o pngutil.o
$(CC) main.o pngutil.o -o app $(FLAGS) $(INCLUDE) -lpng
main.o: main.c
$(CC) -c main.c $(FLAGS) $(INCLUDE)
pngutil.o: pngutil.c pngutil.h
$(CC) -c pngutil.c $(FLAGS) $(INCLUDE)
clean:
rm -f main.o pngutil.o
.PHONY = clean
This demonstrates key concepts like targets, dependencies, variables, compiling individual objects and linking.
Using Makefiles is essential for non-trivial programs with modularity and reuse of components across files.
Debugging with gdb
GDB can be used for interactive debugging when issues arise during compilation.
Common gdb Commands:
- run – Begin executing the debugged program
- list – Show lines of code around current line
- break – Set breakpoint at function or line number
- next – Step over next statement
- step – Step into functions called
- print – Evaluate expressions
- quit – Exit gdb
Say our PNG handling app crashes with a segmentation fault. We can debug it with gdb:
gdb ./app
gdb> break pngutil.c:134 // Set breakpoint before crash line
gdb> run // Execute until breakpoint
gdb> next // Step over statements in pngutil
gdb> print width // Check variable values
gdb> quit
// Fix issue after inspection
GDB allows inspection of running programs and narrowing down bugs quickly.
Compiling More Complex Programs
While a basic toolchain is installed with build-essential, compiling larger programs like databases, interpreters etc needs additional dependencies.
Some examples:
Nginx from Source
To compile the Nginx web server, PCRE (regex library) and OpenSSL dependencies are required.
sudo apt install libpcre3 libpcre3-dev openssl libssl-dev
./configure
make
sudo make install
Python C Extension Module
For compiling Python extension modules written in C, corresponding dev libraries are needed.
sudo apt install python3-dev
gcc -c -fPIC module.c -I/usr/include/python3.8
gcc -shared module.o -o module.so
This produces module.so that can be imported in Python.
OpenGL Glut Programs
For OpenGL development with GLUT:
sudo apt install freeglut3-dev
gcc program.c -lGL -lglut -lGLEW -lm
This way required dev libraries for the target program must be installed.
Cross Platform Compilation with GCC
GCC supports cross compiling code for other architectures like embedded devices running non-Linux systems.
For example, to compile a C program for ARM devices:
arm-linux-gnueabi-gcc -c code.c -o code.o
Advantages include compiling natively on fast x86 devices, no need to install dev tools on target, flexibility to test for different architectures etc.
Cross compiling needs the gcc cross compiler, kernel/C library headers and symlinks to system root for the target. Packages like gcc-arm-linux-gnueabi provide this.
Integrating Compile Toolchain into Environments
The build essentials can be integrated into:
Docker
A Dockerfile can install build essentials with:
FROM ubuntu:latest
RUN apt-get update \
&& apt-get install -y build-essential
This results in reproducible builds across different host systems.
Vagrant VMs
For consistent dev environments with Vagrant:
Vagrant.configure("2") do |config|
config.vm.provision "shell", inline: "apt-get install -y build-essential"
end
Now builds work reliably across the team with standardized compiler toolchain in the VM.
Such environments provide portability across different OS versions for compiling code.
Potential Issues and Troubleshooting
Some common compilation issues faced along with their fixes:
Header File Not Found Error
Error like:
fatal error: zlib.h: No such file or directory
Fix is to install the required package containing the missing header:
sudo apt install zlib1g-dev
Library Version Mismatch
Linker errors during compilation about missing symbols in libraries.
This arises when two different major versions of the same library are installed and being linked against.
Identify the incompatible libraries from error messages and force link the correct version.
Failed to Run Compiled Program
Dynamic linker errors like:
./program: error while loading shared libraries: libxyz.so.1: cannot open shared object file: No such file or directory
Export the shared library path before launching:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
Or copy .so files to locations the linker searches by default.
Circular Dependencies Issue
Error message:
gcc: error trying to exec ‘cc1plus‘: execvp: No such file or directory
This can happen when autoconf, automake, bison and flex circularly depend on each other during installation.
Fix by breaking the cycle and installing one by one manually starting at the base package.
Many such issues can come up during complex compilations. Identifying the exact cause from Errors and debugging systematically helps.
Best Practices for Production Grade Code
Some key things to ensure for compiling high quality and reliable software:
- Use all compiler warnings and debugging info (gcc‘s -Wall, -g)
- Test edge cases thoroughly with fuzzing frameworks
- Profile bottlenecks with tools like gprof
- Harden security with flags like -fstack-protector
- Check for memory leaks and errors with valgrind
- Optimize performance for specific CPU with -march
- Validate 32/64 bit compatibility for libraries
- Follow standards for clear and maintainable code
Production grade code has high stability, security and efficiency aided by the build process.
Conclusion
The build-essential packages provide fundamental tools for compiling code in Linux like gcc, make etc. Installing them allows creating executables from source code.
We covered working of key components, installing build essentials via apt, verifying with test C program compilation, using advanced Makefiles and gdb for debugging.
We also explored dependencies for real world programs, cross compilation needs, integrating toolchain into Docker/Vagrant environments. Finally troubleshooting issues and production grade compilation best practices were discussed.
This 2600+ word comprehensive guide will help setup a robust code compilation environment using build essentials for programmers and Linux enthusiasts. The expert tips shared will aid debugging and shipping quality software.


