Master the fundamental concepts of c programming deep dive through this focused micro-challenge.
A C file on disk is not yet a running process. Clang (via the gcc driver) walks four stages that Make, CMake, and Bazel all assume you understand.
.c → .i): expand #include, #define, strip comments (gcc -E).i → .s): parse C, emit assembly (gcc -S).s → .o): machine code in a relocatable object (gcc -c).o → executable): resolve symbols across translation units (gcc file.o -o prog)Each .c file is its own translation unit. add.o can compile while main.o is untouched, which is why incremental builds work.
bashLoading…
"Undefined reference to foo" is a linker error: something declared, never defined in any .o. "Unknown type name" is a compiler error in stage 2.
For this exercise, you will run a small program with macros through all four stages and report what changed. This task asks you to connect error messages to stages, because debugging build failures without that map wastes hours in every systems shop.
gcc turns C into an executable in four stages: preprocess (-E), compile to assembly (-S), assemble to an object file (-c), and link. The first stage is pure text rewriting, and most "why does my macro do that?" bugs live there. Implement the core of it: a mini C preprocessor that reads a source file on stdin and prints what gcc -E -P would print.
A directive is a line whose first non-blank character is # (spaces are allowed after it).
#define NAME body: object-like macro.#define NAME(a, b) body: function-like, only if ( directly touches the name. #define F (x) x is object-like with body (x) x. Up to 8 parameters, and NAME() takes none. Redefining a macro replaces it.#undef NAME.#ifdef NAME, #ifndef NAME, #else, #endif (nestable). Lines in a false branch are dropped, including #defines.#include <stdio.h>) is printed unchanged, since there are no headers here.// to the end of the line, and /* */. A block comment that spans lines joins them into one line, as in clang's output. String and character literals are never touched, and neither are the insides of numbers like 0x1F or 1e10.(: collect the arguments up to the matching ) on the same line, split at top-level commas (parentheses nest, and commas inside literals don't count), and trim them. Fully expand each argument first, then substitute it for its parameter.#define LOOP LOOP + 1 stops after one step.line N: error: MAC expects K argument(s), got M;line N: error: unterminated argument list for MAC;line N: error: #else without #if, #else after #else, #endif without #if;line N: error: macro name missing;line N: error: bad parameter list for MAC;line N: error: #ifdef needs a macro name (or #ifndef).error: unterminated comment at end of input if a block comment never closes, then error: K unterminated conditional(s) at end of input if any are still open, then always -- N macro(s) defined, M expansion(s). N counts the macros still defined, and M counts every replacement, nested ones included.Input:
cLoading…
Output:
cLoading…
clang -E -P on the same input (apart from #include, which is passed through).MAX(MAX(1, 2), 3) must expand both calls.Hidden tests cover self-referential and mutually recursive macros, macros passed as arguments, commas inside parentheses and strings, numbers that look like identifiers, #undef, empty-parameter macros, nested conditionals, a comment spanning lines, and every error.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it works