3

Рассмотрим следующий пример:

// file header1.h
#include <iostream>

void foo()
{
    std::cout << "Foo" << '\n';
}

//  file source1.cpp
#include "header1.h"


//  file main.cpp
#include "header1.h"

int main()
{
    return 0;
}

В связи с тем, что определение foo находится в хедере (что не очень хорошо), который включён в более чем один .cpp файл, линкер честно выдает ошибку наподобие:

multiple definition of foo ... first defined in main.o ...

Одно из решений - сделать foo статической функцией, что обеспечит одно определение в объектных файлах:

static foo() { ... }

второе:

inline foo() { ... }

третье:

static inline foo() { ... }

Вопросы:

  1. Почему inline функция тоже решает эту проблему?

  2. Какие принципиальные отличия у этих решений?

αλεχολυτ
  • 28,987
  • 13
  • 60
  • 119
ampawd
  • 3,701

2 Answers2

6
  1. inline решает эту проблему потому, что спецификация языка этого требует. Правило Одного Определения (One Definition Rule) для inline функций ясно говорит, что inline функция с внешним связыванием может определяться в программе много раз в разных единицах трансляции (точнее: должна определяться везде, где одна используется и везде - одинаково).

    А уж как компиляторы/линкеры реализуют это требование - это их проблемы. Фактически, если такая функция породила тело в нескольких объектных файлах (т.е. не все ее вызовы заинлайнились), то линкеру будет предписано не ругаться на тему "multiple definition", а молча выбрать одно-единственное тело и остальные выбросить.

    Так как "классический" формат объектного файла не позволяет определить, где начинается и где заканчивается тело конкретной функции, для целей возможного будущего удаления компилятор их обычно помещает в выделенные секции объектного файла.

  2. Принципиальное отличие между inline и static вариантом заключается в том, что inline функция сохраняет внешнее связывание (external linkage) и ведет себя во всех отношениях как функция со внешним связыванием. Например, если разные единицы трансляции попробуют взять адрес inline функции, то везде обязательно должно получиться одно и то же значение. Статические переменные, определенные внутри такой inline функции тоже существуют в единственном экземпляре во всей программе, как и в любой другой функции со внешним связыванием.

    В static варианте наоборот, функция получает внутреннее связывание (internal linkage) - каждая единица трансляции получает свою копию функции со своим уникальным адресом.

    Принципиальных отличий между static inline и static нет, с той только разницей, что в первом случае дается подсказка компилятору.

  • Хорошо бы добавить про статические переменные внутри static функции для сравнения с оными в inline функции. – αλεχολυτ Jul 20 '17 at 07:27
3
  1. это очевидно из самой сути inline -- если код такой функции в самом деле "заинлайнен", то на него существует (логически) только одна ссылка (а именно в том месте, где код "инлайнится"), следовательно никаких проблем с "multiple definition" (т.е. проблемы выбора той или иной точки входа) не существует.

  2. тут принципиальное отличие возникает при наличии static переменных у таких (inline и static inline функций. В каждой единице компиляции у static inline будут свои экземпляры таких переменных (а у "не static" один экземпляр static переменной на весь загрузочный модуль (результат линковки)).

avp
  • 46,098
  • 6
  • 48
  • 116
  • 1
    Насколько я помню, inline означает не «подставлять тело функции вместо вызова», а «игнорировать one definition rule». – VladD Jul 19 '17 at 20:48
  • @VladD в целом, inline-функция должна вставляться в код целиком, вместо кода ее вызова... – Владимир Мартьянов Jul 19 '17 at 20:55
  • 1
    @ВладимирМартьянов: Согласно стандарту, не должна: http://eel.is/c++draft/dcl.inline#2 – VladD Jul 19 '17 at 21:59
  • 1
    Ну то есть не обязательно должна. – VladD Jul 19 '17 at 22:11