This comes around all the time, so here is an actual build which covers the permutations of this:
#- Makefile -----
all: 1 2 3 12 13 23
-./1
-./2
-./3
-./12
-./13
-./23
1: main.o 1.o; $(CC) main.o 1.o -o $@
2: main.o 2.o; $(CC) main.o 2.o -o $@
3: main.o 3.o; $(CC) main.o 3.o -o $@
12: main.o 1.o 2.o; $(CC) main.o 1.o 2.o -o $@
13: main.o 1.o 3.o; $(CC) main.o 1.o 3.o -o $@
23: main.o 2.o 3.o; $(CC) main.o 2.o 3.o -o $@
/* ---- 1.c ----- */
int num;
/* ---- 2.c ----- */
extern int num;
/* ---- 3.c ----- */
int num = 2;
/* ---- main.c --- */
#include <stdio.h>
extern int num;
int main() {
printf("%d\n", num);
return 0;
}
Ok, and after you have surgically deconstructed this SO-archive, and run:
make -i all
You should get an output something like:
cc main.o 2.o -o 2
Undefined symbols for architecture x86_64:
"_num", referenced from:
_main in main.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: [2] Error 1 (ignored)
./1
0
./2
make: ./2: No such file or directory
make: [all] Error 1 (ignored)
./3
2
./12
0
./13
2
./23
2
So, first up, the program 2 doesn't build. Why not? Because 2.c contains an explicit declaration of an external variable (num); so when linked with main.c, both have expressed an interest in num; but neither have defined it.
In contrast, both ./1 and ./12 have managed to define it, but have not assigned it a value, thus it is defined as zero. The lack of extern in 1.c provides the impetus for the linker to shrug and say, ok, I will just allocate space for it in the zero section (bss).
./3 ./13 ./23 demonstrate that in the presence of a defining statement (int num = 2;) we see the expected result.
What is left out, but may be a good exercise, is what happens if you:
$(CC) main.c 1.c 1.c 1.c -o 111
? Do you get an error? Why not?