How to check the value of a variable in C
printf is the obvious answer and the one that misleads you. Format specifiers, why the debugger beats print statements, and how the optimiser makes variables disappear.

The quick answer is printf, and the trap is that the wrong format specifier prints a plausible wrong number rather than failing.
#include <stdio.h>
int main(void) {
int count = 42;
double ratio = 0.75;
char *name = "sensor";
printf("count = %d\n", count);
printf("ratio = %f\n", ratio);
printf("name = %s\n", name);
return 0;
}Format specifiers, and why mismatches are dangerous
C does not check that your specifier matches your argument. A mismatch is undefined behaviour — often garbage, sometimes a crash, occasionally a value that looks right until it does not.
%d— int;%u— unsigned int%ld— long;%lld— long long%zu—size_t. Using%dhere is a very common bug on 64-bit systems.%f— double (a float is promoted to double, so%fis correct for both)%p— pointer, cast to(void *)%c— char;%s— a null-terminated string, never a single char%x— hexadecimal, invaluable for flags and masks
size_t len = strlen(buffer);
printf("len = %zu\n", len); // correct
printf("len = %d\n", len); // wrong, and may print nonsense
printf("ptr = %p\n", (void *)&count); // the cast is requiredTurn the warnings on and the compiler will catch these:
gcc -Wall -Wextra -Wformat=2 -g -O0 program.c -o programPrint to stderr for diagnostics
stdout is line-buffered to a terminal and fully buffered to a pipe or file. If the program crashes, buffered output is lost — so the print statement before the crash never appears, and you conclude the crash happened earlier than it did.
fprintf(stderr, "reached parse loop, i = %d\n", i); // unbufferedThis one change removes a whole class of misleading debugging sessions.
A debug macro that can be switched off
#ifdef DEBUG
#define DBG(fmt, ...) \
fprintf(stderr, "%s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#else
#define DBG(fmt, ...) ((void)0)
#endif
DBG("count = %d, ratio = %.3f", count, ratio);Compile with -DDEBUG to enable it. The file and line come free, and the release build contains none of it.
The debugger is better than printing
Print statements require you to guess in advance which variable matters. A debugger lets you ask afterwards.
gcc -g -O0 program.c -o program
gdb ./program(gdb) break parse_packet # stop when this function is called
(gdb) run
(gdb) print count # inspect any variable in scope
(gdb) print *ptr # follow a pointer
(gdb) print arr[3]@5 # five elements starting at index 3
(gdb) watch total # stop whenever total changes
(gdb) backtrace # how did we get here
(gdb) finish # run to the end of this framewatch is the one worth knowing. "This value is wrong and I don't know who wrote it" is answered directly by a watchpoint, and awkwardly by print statements.
When the variable has vanished
With optimisation enabled, gdb may report <optimized out>. The variable was kept in a register or eliminated entirely — the code is correct, but there is no storage to inspect. Build with -O0 -g while debugging.
The related trap: a bug that disappears at -O0 and returns at -O2 is usually undefined behaviour, not a compiler bug. Run it under a sanitizer before blaming the optimiser:
gcc -g -O1 -fsanitize=address,undefined program.c -o program && ./programThat will name the line and the fault far faster than adding print statements around it.

