Introduction#

So you’re starting a C course. Congratulations, you’re about to spend a lot of quality time with segmentation faults and loose all of your hair. Before any of that, though, there’s one boring-but-critical thing nobody warns you about: your setup has to match the grader’s setup.

Here’s the trap. Your code doesn’t get a grade because it works on your machine. It gets a grade because it compiles and runs on the grader’s machine, with the grader’s compiler and the grader’s flags. Those are almost never the same as your defaults, and the gap has gotten wider recently: modern compilers no longer default to C99. Fresh GCC (version 15 and up) defaults to C23, and Apple’s clang on macOS defaults to C17. Neither of those is C99.

Why do you care? Because C23 quietly hands you features that plain C99 doesn’t have. Write bool x = true; with no #include, it’ll compile on your fancy new compiler and you’ll feel great — then the grader’s -std=c99 build spits error: unknown type name 'bool' and you eat a zero for something that “worked.” Fun.

This post fixes that once, at the start, so you never think about it again.

Here’s what we’ll cover:

  1. Check the grader’s setup first

  2. Get a Unix-like environment

  3. Always pass -std=c99 explicitly

  4. Set up your editor

  5. Write a tiny Makefile

  6. Learn the debugging tools early

  7. Use git (yes, even alone)

  8. A complete, copy-pasteable example


1. Check the Grader’s Setup First#

Before you touch anything, find out which compiler and flags the grader uses and match them exactly. It’s usually written in the assignment brief or the course page. If it isn’t, ask. This is the single highest-value thing in this whole post.

Most autograders run GCC on Linux. If that’s your case, your life is easy: get Linux (or something that behaves like it, like WSL) and use GCC. Don’t be the person who develops in some cursed Windows IDE all semester and discovers on submission night that their code doesn’t build on the grader.

Match the flags too, not just the compiler. If the brief says -std=c99 -Wall -Werror, then -Werror means every warning is a hard error. A warning you’ve been ignoring all week is suddenly a failed build. Know this now.


2. Get a Unix-like Environment#

C is a Unix language. Fighting that fact will only hurt you. Pick your platform:

Windows — Do not develop directly on Windows for a Linux grader. Install WSL2 with Ubuntu (it’s built in, simply run wsl --install from an admin PowerShell), then inside Ubuntu:

sudo apt update
sudo apt install build-essential gdb valgrind

Avoid MinGW or Visual Studio for this unless the course specifically requires them. They’re fine compilers, they’re just not the grader’s compiler, and that’s the entire point of this article.

macOS — Install the command line tools:

xcode-select --install

Heads up: on macOS gcc is a lie — it’s actually Apple clang wearing a gcc name tag. That’s genuinely fine for C99, clang speaks it perfectly. Just remember it defaults to C17, not C99, so you still have to pass the flag (we’re getting to that).

Linux — You’re already home. Install the toolchain:

sudo apt install build-essential gdb valgrind

(Or your distro’s equivalent — dnf groupinstall "Development Tools", pacman -S base-devel, you know the drill.)

⚠️ WARNING: Prolonged exposure to LINUX may result in unexplained furry tendencies. Exercise caution.


3. Always Pass -std=c99 Explicitly#

This is the hill. Say it with me: I will always pass -std=c99.

Good boy.

Without it, your compiler uses its default standard, and as we covered, that’s C23 or C17 now — not C99. Here’s the receipt. This compiles fine under a modern GCC’s default:

int main(void) {
    bool ready = true;   // works in C23 with no include
    return ready ? 0 : 1;
}

But the exact same code under -std=c99:

error: unknown type name 'bool'
note: 'bool' is defined in header '<stdbool.h>'; this is probably fixable by adding '#include <stdbool.h>'

In C99, bool isn’t a keyword — you have to #include <stdbool.h> to get it. In C23 it’s baked into the language. If you develop against C23 and submit to C99, you’ll trip over stuff like this constantly and have no idea why. Pin the standard and the problem evaporates.

Your day-to-day development command:

gcc -std=c99 -Wall -Wextra -pedantic -g -fsanitize=address,undefined main.c -o main

What all that alphabet soup buys you:

Flag What it does
-std=c99 Pins the language standard. The whole reason we’re here.
-Wall -Wextra Turns on the warnings that actually catch your bugs. Read them.
-pedantic Complains about non-standard extensions, keeping you honest and portable.
-g Debug symbols, so gdb can show you real line numbers instead of hex garbage.
-fsanitize=address,undefined The good stuff — see below.

The sanitizers (-fsanitize=address,undefined) instrument your program to catch out-of-bounds access, use-after-free, and undefined behavior at runtime, printing a clean report with the exact line instead of a cryptic Segmentation fault (core dumped). Once pointers and malloc show up in your course, these will save your sanity. Turn them on early.

⚠️ Platform caveat about leak detection. AddressSanitizer works on macOS, but its leak detection (LeakSanitizer) does not work with Apple clang on macOS — it’s simply not wired up there. If you need to hunt memory leaks on a Mac, use valgrind… except you can’t, because valgrind is Linux/WSL only too. On macOS your realistic move is to do leak-hunting inside your WSL/Linux/VM environment. On Linux both work and life is good. This is due to the fact that the furries working on linux are more locked in than the ones on macOS.


4. Set Up Your Editor#

You don’t need a heavyweight IDE. VS Code with the clangd extension is a great, lightweight combo that gives you real C diagnostics, go-to-definition, and autocomplete. Even notepad could be a great IDE if Microslop didn’t put so much AI up its ass.

The catch: clangd needs to know you’re targeting C99, or its squiggly red underlines will disagree with your actual build. Drop a file named compile_flags.txt in your project folder:

-std=c99
-Wall
-Wextra

Now the editor’s diagnostics follow the same rules as your compiler, and you stop getting phantom errors (or worse, phantom approvals) for C23 features that won’t survive the grader.


5. Write a Tiny Makefile#

Retyping that long gcc command every single time is how you develop a stress twitch or a pegging fetish. From your very first assignment, drop a small Makefile in the project so you just type make:

CC     = gcc
CFLAGS = -std=c99 -Wall -Wextra -pedantic -g -fsanitize=address,undefined

main: main.c
	$(CC) $(CFLAGS) -o $@ $^

clean:
	rm -f main

Now make builds and make clean tidies up. $@ is the target name (main), $^ is the prerequisites (main.c) — cute little automatic variables so you don’t repeat yourself.

⚠️ The one thing that will drive you insane: the indented recipe lines under main: and clean: must start with a real TAB, not spaces. Make is militant about this and the error it gives you (*** missing separator) is famously unhelpful. If your editor auto-converts tabs to spaces, go turn that off for Makefiles right now.

Congratulations, you are now on your way to become either a femboy or a furry.


6. Learn the Debugging Tools Early#

printf debugging will carry you further than you’d think, but it hits a wall the moment you’re deep in pointers. Learn the real tools before you loose the plot:

  • gdb (or lldb on macOS) — step through your code line by line, inspect variables, and see exactly where it blows up. Compile with -g (you already are) and run gdb ./main.
  • valgrind — the memory-leak bloodhound. Run valgrind ./main and it reports every byte you malloc’d and forgot to free. Linux/WSL only, as mentioned.

Both of these become essential the instant malloc and pointers enter your course. Don’t wait.


7. Use Git#

Use git. Yes, even for a solo assignment nobody else will ever see.

git init
git add .
git commit -m "Assignment 1: it compiles and prints the right thing"

Commit after each thing that works. When you inevitably “improve” something at midnight and detonate the whole program, git is the difference between git restore and quietly crying. It’s free insurance. Take it.


8. Putting It All Together#

Here’s a complete, working project. Three files in one folder. This exact example was compiled clean with -std=c99 -Wall -Wextra -pedantic (zero warnings) and run before publishing, so if it doesn’t work you shall eek psychiatric help.

main.c — note it uses genuinely C99 things: // comments, the loop variable declared inside the for, and bool obtained the correct C99 way via <stdbool.h>:

#include <stdio.h>
#include <stdbool.h>

// Returns true if n is prime. In C99 we can declare the loop
// variable inside the for statement and use // comments.
static bool is_prime(int n) {
    if (n < 2) return false;
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) return false;
    }
    return true;
}

int main(void) {
    printf("Primes under 20: ");
    for (int n = 2; n < 20; n++) {
        if (is_prime(n)) printf("%d ", n);
    }
    printf("\n");
    return 0;
}

Makefile (recipe lines are TABs, remember):

CC     = gcc
CFLAGS = -std=c99 -Wall -Wextra -pedantic -g -fsanitize=address,undefined

main: main.c
	$(CC) $(CFLAGS) -o $@ $^

clean:
	rm -f main

compile_flags.txt (for the editor):

-std=c99
-Wall
-Wextra

Build and run:

make
./main

Output:

Primes under 20: 2 3 5 7 11 13 17 19 

When you’re done and want a clean folder:

make clean

That’s a real, standards-compliant C99 project. Copy the structure into every assignment.


The Checklist#

Tick these off before you write a single line of assignment code:

  • I found out the grader’s exact compiler and flags
  • I have a Unix-like environment (WSL2 / macOS CLT / Linux)
  • build-essential (or equivalent) + gdb (+ valgrind on Linux/WSL) installed
  • I pass -std=c99 on every build
  • -Wall -Wextra -pedantic are on, and I actually read the warnings
  • compile_flags.txt sits in the project so my editor agrees with my compiler
  • A Makefile exists and its recipe lines start with tabs
  • I can build with make and run the result
  • The project is a git repo and I commit when things work

Conclusion#

None of this is hard, and it’s the kind of thing you set up once and forget. To recap:

  1. Match the grader’s compiler and flags — this is everything.

  2. Get a proper Unix-like environment.

  3. Always, always pass -std=c99.

  4. Let your editor and a Makefile do the boring parts.

  5. Learn gdb and valgrind before you’re desperate.

  6. Commit early, commit often.

Do this on day one and you’ll never lose marks to “but it worked on my machine.” You’ll lose them to actual bugs instead, like a real programmer.

References: cppreference.com’s C section marks which features belong to which C version — invaluable for checking “is this actually C99?” The C Programming Language (K&R) is a classic and worth reading, but it covers C89, so pair it with something newer for the modern standard bits.

Now go segfault responsibly. Happy compiling!

— KMiguel