O GCC armazena o que gera onde quer que você (ou neste caso, o Makefile) o informe (com a opção de saída -o ). Ele não faz o cache de resultados de compilação sozinho.
O que torna possíveis recompilações parciais é o próprio Makefile. É um conjunto de alvos (executáveis, arquivos objeto, possivelmente outras coisas) com o que eles dependem e regras para gerar o alvo a partir dessas dependências.
make examina tudo o que precisa ser construído, avaliando as cadeias de dependências e reconstruindo todo o destino que ainda não existe ou com dependências mais recentes que o destino.
Por exemplo, digamos que você tenha este Makefile simples:
foo.o: foo.c foo.h
gcc -o foo.o -c foo.c
foo: foo.o
gcc -o foo foo.o
Se você invocar make foo , ele verá o Makefile, veja que foo depende de foo.o . Portanto, ele procura foo.o e vê que isso depende de foo.c e foo.h . Se foo.o for mais antigo que um desses dois arquivos, ou se foo.o não existir, ele executará a linha de compilação gcc -o foo.o -c foo.c , que gera foo.o .
Se você reexecutar make foo depois disso, foo.o será mais recente que o arquivo C e o cabeçalho, então make não recompilará foo.o e pulará para vincular o executável final ( se isso for necessário - pode-se dizer que nada precisa ser feito).