pmg ответ очень убедительный. Если вы хотите знать, как работают статические объявления на уровне объектов, эта информация может быть вам интересна.
Я повторно использовал ту же программу, написанную pmg, и скомпилировал ее в файл .so (общий объект)
Следующее содержимое после сброса .so файла в нечто удобочитаемое для человека
0000000000000675 f1 : адрес функции f1
000000000000068c f2 : адрес функции f2 (staticc)
обратите внимание на разницу в адресе функции, это что-то значит. Для функции, которая объявлена с другим адресом, она может очень хорошо показать, что f2 живет очень далеко или в другом сегменте объектного файла.
Линкеры используют что-то, называемое PLT (таблица связывания процедур) и GOT (глобальная таблица смещений), чтобы понимать символы, к которым у них есть доступ для ссылки.
А пока подумайте, что GOT и PLT магически связывают все адреса, а динамический раздел содержит информацию обо всех этих функциях, которые видны компоновщику.
После выгрузки динамического раздела .so файла мы получаем несколько записей, но заинтересованы только в функциях f1 и f2 .
Динамический раздел содержит запись только для функции f1 по адресу 0000000000000675 и не для f2 !
Num: Значение Размер Тип Bind Vis Ndx Имя
9: 0000000000000675 23 FUNC GLOBAL DEFAULT 11 f1
И это все! Из этого ясно, что компоновщику не удастся найти функцию f2 , поскольку ее нет в динамическом разделе .so файла.