Debug vs. Release in C

Debug vs release builds

While developing a program, we often want as much information as possible when something goes wrong.

Once the program is finished, we usually care more about performance and executable size.

For this reason, software is often built in either debug or release mode.

Debug builds

A debug build prioritises finding and fixing problems.

Typical features include:

  • debug information for a debugger
  • additional checks
  • debug messages
  • little or no optimisation

A typical compiler command might be:

gcc -g -O0 main.c -o main

Here:

-g

adds debugging information, while:

-O0

disables compiler optimisations.

This makes debugging easier because the generated machine code stays close to the original source code.

Release builds

A release build prioritises performance and executable size.

Typical features include:

  • compiler optimisations enabled
  • debug information removed
  • debug messages disabled

For example:

gcc -O2 main.c -o main

The exact optimisation level depends on the project, but release builds generally produce smaller and faster executables.

Enabling debug code

Sometimes it is useful to include code that only runs in a debug build:

#ifdef DEBUG
printf("temperature = %.1f\n", temperature);
#endif

This code only exists when DEBUG has been defined.

For example:

gcc -DDEBUG main.c -o main

The -D option defines a preprocessor symbol.

In this case:

DEBUG

is defined, so the printf() statement is compiled into the program.

Without:

-DDEBUG

the debug print is removed completely.

Example

#include <stdio.h>

int main(void)
{
    int value = 42;

#ifdef DEBUG
    printf("Debug: value = %d\n", value);
#endif

    printf("Program running\n");

    return 0;
}

Debug build:

gcc -DDEBUG main.c -o main

Output:

Debug: value = 42
Program running

Release build:

gcc main.c -o main

Output:

Program running

Debug and release targets in a Makefile

In practice, we normally do not type the debug and release compiler options manually.

Instead, a Makefile often provides separate targets:

debug:
	gcc -g -O0 -DDEBUG main.c -o main

release:
	gcc -O2 main.c -o main

We can then build the debug version using:

make debug

or the release version using:

make release

This keeps the build settings in one place and avoids repeatedly typing long compiler commands.

Larger projects often have additional targets such as:

make test
make debug
make release
make clean

The exact names vary between projects, but the idea is always the same: different targets build the project with different settings.

A useful rule

While developing:

Build for debugging.

When releasing software:

Build for performance.

The exact settings vary between projects, but the idea is always the same: debug builds help find problems, while release builds focus on the final program.


This site uses Just the Docs, a documentation theme for Jekyll.