Так что теперь мне интересно, должен ли этот код скомпилироваться:
Вам интересно, эта полная программа "должна" скомпилировать ?
void f();
void g() { auto ff = &f; }
int main() {}
Если вы предполагаете, что он скомпилируется, " сколько раз " вы решите, что код скомпилируется? (Это единственная истина или двойная независимая истина, которую он должен компилировать?)
f()
«используется» неиспользуемым способом (адрес назначается локальной переменнойэто оптимизировано далеко).
g():
ret
g()
сам по себе даже не используется
Любой «тупой» компоновщик может увидеть, что, поскольку g()
не нужен, f()
не нужен;при этом предполагается, что «тупой» компилятор предполагает, что f()
даже необходим в g()
!
И все же необъявленная функция (f()
) явно использовалась ODR . Вы ожидали такого поведения реализации или «удивляетесь» и задаете вопросы по этому поводу?
Если вы не ожидали ошибок, вам, вероятно, следует переосмыслить это целое "нарушение использования ODR, которое проходит мимокомпилятор заставляет меня задуматься "вещь :
Реализация не диагностирует отсутствие определения, которое им не нужно для создания работающей программы.
Что они потребность зависит от хитрости реализации, так как очевидно, что аргумент 1. выше можно усложнить, если в сложном коде нет необходимости использовать f()
в g()
. Я намеренно привел тривиальный пример, допустимый для -O0
.
[Примечание: это сборка для g()
на уровне -O0
:
g():
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], OFFSET FLAT:_Z1fv
nop
pop rbp
ret
Нет зависимости от символа f()
, как вы можете видеть.]
Знания зависят от уровня оптимизации и от объема данных, предоставляемых во время генерации кода (может помочь компиляция множества блоков перевода одновременно).
Дляне виртуальные функции, функция либо необходима (то есть с именем ) необходимой вещью (другой функцией или глобальным объектом, инициализированным с помощью адреса этой функции).
Для виртуальных функций получение знанийгораздо сложнее: знать, что переопределение не будет вызываться, не так тривиально, как с функцией, которая не является переопределением (включая не виртуальную функцию), так как переопределение можно вызывать с помощью «позднего связывания», когдавиртуальный вызов делается на (g) lvalue.
Компилятор может или не может точно определить, какие виртуальные функции никогда не нужны, в зависимости от сложности программы (проблема остановки программы показывает, что они никогда не будут знать точно для всех программ), но нестатические функции-члены объектов, которые никогда не создаются, очевидно, никогда не нужны .