extern
необходим, потому что он объявляет, что символ существует и имеет определенный тип, и не выделяет для него память.
Если вы делаете:
int foo;
В заголовочном файле, который используется несколькими исходными файлами, вы получите ошибку компоновщика, поскольку для каждого источника будет создана собственная копия foo, и компоновщик не сможет разрешить символ.
Вместо этого, если у вас есть:
extern int foo;
В заголовке будет объявлен символ, определенный в другом месте каждого исходного файла.
Один (и только один) исходный файл будет содержать
int foo;
, который создает единственный экземпляр foo для разрешения компоновщиком.