Встроенные функции имеют не встроенную копию

В руководстве 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, Но из этого вопроса и ответов я делаю вывод, что это полезно только для демонстрации намерений. Таким образом, с одной стороны, ясно, что это не просто намерение без использования оптимизации, поскольку оно создает не встроенную копию. Но с другой стороны, с оптимизацией это действительно только показывает намерение, так как не встроенная копия удаляется. Это смущает.

Мои вопросы:

  1. Правильно ли GCC удаляет не встроенную копию с включенной оптимизацией? Другими словами, всегда должна быть не встроенная копия, если я не использую static inline?
  2. Если я хочу быть уверенным, что нет не встроенной копии, я должен использовать 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 ключевое слово.

Другие вопросы по тегам