C rewards discipline. These habits and tools prevent most of the bugs covered in these notes.
Compile and test
| Practice | How |
|---|---|
| Maximum warnings | -Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wformat=2, and -Werror in CI |
| Sanitizers in debug/test builds | -fsanitize=address,undefined (and thread for concurrent code) |
| Memory checking | valgrind --leak-check=full ./app |
| Static analysis | clang --analyze, clang-tidy, cppcheck, gcc -fanalyzer |
| Debugger | gdb / lldb with -g -O0: breakpoints, bt, print, watchpoints |
| Fuzzing | libFuzzer / AFL++ for parsers and anything taking untrusted input |
| Formatting | clang-format — never argue about braces again |
| Hardening | -D_FORTIFY_SOURCE=2 -O2 -fstack-protector-strong |
Coding habits
A defensive pattern in one place
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *data;
size_t len;
} Text;
/* returns 0 on success; on failure *out is untouched and nothing leaks */
int text_read_file(const char *path, Text *out) {
int rc = -1;
char *buf = NULL;
FILE *f = fopen(path, "rb");
if (!f) goto done;
if (fseek(f, 0, SEEK_END) != 0) goto done;
long size = ftell(f);
if (size < 0) goto done;
rewind(f);
buf = malloc((size_t)size + 1);
if (!buf) goto done;
if (fread(buf, 1, (size_t)size, f) != (size_t)size) goto done;
buf[size] = '\0';
out->data = buf;
out->len = (size_t)size;
buf = NULL; /* ownership transferred to *out */
rc = 0;
done:
free(buf); /* free(NULL) is fine */
if (f) fclose(f);
return rc;
}
int main(void) {
FILE *f = fopen("note.txt", "w");
if (!f) return 1;
fputs("clean C", f);
fclose(f);
Text t = {0};
if (text_read_file("note.txt", &t) == 0) {
printf("%zu bytes: %s\n", t.len, t.data);
free(t.data);
}
printf("missing file -> %d\n", text_read_file("missing.txt", &t));
return 0;
}7 bytes: clean C
missing file -> -1Debugging workflow
$ gcc -g -O0 -Wall -Wextra -fsanitize=address,undefined app.c -o app
$ ./app # sanitizer reports file:line of the first bad access
$ gdb ./app # or: lldb ./app
(gdb) break parse_line # stop in a function
(gdb) run input.txt
(gdb) next / step / finish # step over / into / out of
(gdb) print *node # inspect memory
(gdb) bt # backtrace after a crash
(gdb) watch count # stop when a variable changesHow would you debug a segmentation fault?