Как уже указывалось, вы нарушаете правило одного определения. Это не конец света, но в этом случае нет никаких гарантий от C ++ - стандарта, что произойдет, и поведение зависит от деталей реализации компоновщика и загрузчика.
Цепочки инструментов и операционные системы сильно различаются, поэтому вышеприведенное даже не будет ссылаться на Windows Но если вы говорите о Linux с обычной парой компоновщик / загрузчик, то поведение будет заключаться в использовании измененной версии - и это будет для каждой установки Linux.
Так работает компоновщик / загрузчик в Linux (и это поведение широко используется, например, для LD_PRELOAD-trick ):
- Символы в
*.so
слабы, и поэтому определение из *.so
просто игнорируется, если компоновщик находит другое определение где-то еще (в вашем случае в обновленной версии f1.o
).
- во время выполнения загрузчик игнорирует определения из общего объекта, если символ уже связан, то есть известно другое определение. В вашем случае символ
f1
(хорошо, из-за искажения имени у него будет другое имя, но давайте для простоты проигнорируем его) уже связан с определением, которое находится в основной программе и, таким образом, будет использоваться, когда f1
вызывается в *.so
.
Однако такой способ работы очень хрупок, и некоторые незначительные изменения могут привести к другому результату.
A: изменение видимости на скрытое.
Рекомендуется скрывать символы, которые не являются частью общедоступного интерфейса, т.е.
__attribute__ ((visibility ("hidden")))
int f1() {return 1;}
В этом случае используется не перезаписанная версия, а старая. Разница в том, что когда компоновщик видит, что используется скрытый символ, он больше не делегирует его загрузчику для разрешения адреса символа, а непосредственно использует адрес под рукой. Позже мы не сможем изменить определение, которое называется.
B: выполнение f1
было встроенной функцией.
Это может привести к действительно забавным вещам, потому что в некоторых частях разделяемый объект будет использоваться старая версия, а в какой-то части новая версия.
-fPIC
предотвращает встраивание функции, которая не помечена inline
, поэтому вышеприведенное верно только для функции, которая помечена как встроенная в явном виде.
В двух словах: этот прием можно использовать в Linux. Однако в больших проектах вы не хотите иметь дополнительную сложность и пытаетесь придерживаться более устойчивой и простой структуры с одним определением.