C for People Who Already Know How to Code
Build Toolchain Basics
Four programs are hiding behind one gcc invocation
gcc file.c -o program looks like one step. It's actually a pipeline of four, and knowing which stage produced an error tells you immediately what kind of mistake you're looking at.
Translation units and the linker's actual job
Each .c file compiles independently. It's a translation unit, and the compiler proper never looks at any other .c file while compiling it. Every reference to something defined elsewhere (another file, a library) becomes a placeholder the object file's symbol table records but doesn't resolve. The linker's entire job is walking every object file and library you hand it and matching those placeholders to real addresses.
Reading a linker error without panicking
undefined reference to 'foo': the linker never found a definition forfooanywhere it looked. You declared it (a header, a prototype) but never linked the.oor library that defines it, or you never defined it at all.multiple definition of 'foo': the linker foundfoodefined more than once across the object files it's merging. Usually a non-static, non-inlinefunction or a non-externglobal variable sitting in a header that got included into more than one.cfile.
Try it yourself: produce and read both linker errors yourselfโบ
Declare a function in a header, call it from main.c, but don't define it anywhere. Link and read the undefined reference error. Then define a non-static int counter; in a header included by two different .c files, and read the multiple definition error that follows. Recognizing these two error shapes on sight saves real debugging time.
Static vs dynamic linking, the short version
A static library (.a) gets copied bodily into your executable at link time: bigger binary, but it runs with zero dependency on that library being present later. A dynamic/shared library (.so on Linux, .dll on Windows) stays a separate file, resolved when the program loads (or on first call, for lazy binding): smaller binary, shared memory across processes using the same library, and a security patch to the library reaches every program using it without a rebuild. The cost is exactly that: the program now depends on the right version of that .so existing on whatever machine runs it.