defined versus undefined symbols
Every name your code shares across files, like a function or a global variable, is, from the linker's point of view, either provided or wanted. A defined symbol is one whose actual body lives in this object file: here are the real machine instructions of the function, or here is the storage for the variable. An undefined symbol (also called external) is a name this file uses but does not contain: a promise that the thing exists somewhere, with a blank where its address will go once found.
Concretely, when you write the function add and put its code in utils.c, utils.o has a defined symbol named add. When main.c merely calls add without defining it, main.o has an undefined symbol named add. The compiler is perfectly happy with this on its own, because each translation unit is compiled in isolation and a declaration (a function prototype in a header) is enough to promise add exists. The actual matching of the wanting file to the providing file is deferred to the linker.
This split is the heart of how separate files cooperate. Linking is, in essence, the job of pairing every undefined symbol with exactly one defined symbol of the same name, then filling in the blanks with real addresses (relocation). Two predictable failures follow directly: if some undefined symbol has no matching definition anywhere, you get 'undefined reference'; if a symbol is defined in two places at once, the linker cannot choose and reports 'multiple definition'. A frequent beginner trip is putting a variable definition (not just a declaration) in a header that several files include, which manufactures exactly that double definition.
// utils.c DEFINED: here is add's actual code int add(int a, int b) { return a + b; } // main.c UNDEFINED: main wants add, but does not provide it int add(int, int); // a declaration (a promise), not a definition int main(void) { return add(2, 3); }
main.o leaves add undefined; utils.o defines it; the linker pairs the two.
A declaration (a promise that a name exists) is not a definition (the actual thing); putting a real variable definition in a header included by many files causes 'multiple definition' at link time.