Встроенные функции имеют не встроенную копию
В руководстве Agner Fog по оптимизации C++ у него есть раздел "Встроенные функции имеют не встроенную копию", где он пишет
Встраивание функции имеет сложность в том, что та же функция может быть вызвана из другого модуля. Компилятор должен сделать не встроенную копию встроенной функции ради вероятности того, что функция также вызывается из другого модуля. Эта не встроенная копия является мертвым кодом, если никакие другие модули не вызывают функцию. Эта фрагментация кода делает кэширование менее эффективным.
Давайте сделаем тест для этого.
foo.h
inline double foo(double x) {
return x;
}
t1.cpp
#include "foo.h"
double t1(double x) {
return foo(x);
}
main.cpp
#include <stdio.h>
extern double foo(double);
int main(void) {
printf("%f\n", foo(3.14159));
}
компилировать с g++ t1.cpp main.cpp
и работает правильно. Если я сделаю g++ -S t1.cpp main.cpp
и посмотреть на сборку я вижу, что main.s
вызывает функцию, определенную в t1.s
, дела g++ -c main.cpp
а также g++ t1.cpp
и глядя на символы с nm
шоу U _Z3food
в main.o
а также W _Z3food
в t1.o
, Таким образом, ясно, что утверждение Агнера о том, что существует не встроенная копия, является правильным.
Как насчет с g++ -O1 t1.cpp main.cpp
? Это не скомпилировать из-за foo
быть неопределенным. дела g++ -O1 t1.cpp
а также nm t1.o
показывает, что _Z3food
был раздет
Теперь я в замешательстве. Я не ожидал, что g++ удалит не встроенную копию с включенной оптимизацией.
Похоже, что при включенной оптимизации inline
эквивалентно static inline
, Но без оптимизации inline
означает, что генерируется не встроенная копия.
Может быть, GCC не думает, что я когда-нибудь захочу не встроенную копию. Но я могу придумать случай. Допустим, я хотел создать библиотеку, и в библиотеке я хочу функцию, определенную в нескольких единицах перевода (чтобы компилятор мог встроить код функции в каждую единицу перевода), но я также хочу, чтобы внешний модуль связывался с моей библиотекой для быть в состоянии вызвать функцию, определенную в библиотеке. Очевидно, для этого мне понадобится не встроенная версия функции.
Агнер дает одно предложение, если я не хочу, чтобы не встроенная копия использовала static inline
, Но из этого вопроса и ответов я делаю вывод, что это полезно только для демонстрации намерений. Таким образом, с одной стороны, ясно, что это не просто намерение без использования оптимизации, поскольку оно создает не встроенную копию. Но с другой стороны, с оптимизацией это действительно только показывает намерение, так как не встроенная копия удаляется. Это смущает.
Мои вопросы:
- Правильно ли GCC удаляет не встроенную копию с включенной оптимизацией? Другими словами, всегда должна быть не встроенная копия, если я не использую
static inline
? - Если я хочу быть уверенным, что нет не встроенной копии, я должен использовать
static inline
?
Я только что понял, что мог неправильно истолковать заявление Агнера. Когда он говорит function inlinng, он может ссылаться на компилятор, склоняющий код, а не на использование inline
ключевое слово. Другими словами, он мог ссылаться на функции, определенные с extern
а не с inline
или же static
,
например
//foo.cpp
int foo(int x) {
return x;
}
float bar(int x) {
return 1.0*foo(x);
}
а также
//main.cpp
#include <stdio.h>
extern float bar(int x);
int main(void) {
printf("%f\n", bar(3));
}
компилировать с gcc -O3 foo.cpp main.cpp
показывает, что foo
был встроен в bar
но это не встроенная копия foo
который никогда не используется в двоичном виде.
1 ответ
Стандарт гласит, что полное определение inline
Метод должен быть виден в каждой единице перевода, которая его использует:
Встроенная функция должна быть определена в каждой единице перевода, в которой она используется odr, и должна иметь точно такое же определение в каждом случае (3.2). [...] Если функция с внешней связью объявлена встроенной в одной единице перевода, она должна быть объявлена встроенной во всех единицах перевода, в которых она появляется; Диагностика не требуется.
(7.1.2 / 4 в N4140)
Это действительно делает пример в вашем вопросе плохо сформированным.
Это правило также включает каждый TU из любого внешнего модуля, связывающего вашу библиотеку. Им также потребуется полное определение в коде C++, например, путем определения функции в заголовке. Таким образом, компилятор может смело опускать любые "не встроенные копии", если текущему переводу это не нужно.
Что касается уверенности, то копия не существует: стандарт не гарантирует какую-либо оптимизацию, так что дело за компилятором. Как с дополнительным, так и без static
ключевое слово.