Встроенные функции в C ++. В чем смысл?


19

Согласно тому, что я прочитал, компилятор не обязан заменять вызов функции встроенной функции своим телом, но сделает это, если сможет. Это заставило меня задуматься - почему у нас есть встроенное слово, если это так? Почему бы не сделать все функции встроенными по умолчанию и позволить компилятору выяснить, может ли он заменить вызовы телом функции или нет?


1
Here . Стр. 6
EpsilonVector

Ответы:


40

inlineиз C; это не было новым для C ++.

Есть ключевые слова C ( registerи inline), которые были разработаны, чтобы позволить программисту помочь в оптимизации кода. В настоящее время они обычно игнорируются, поскольку компиляторы могут лучше выполнять присваивание регистров и решать, когда вставлять функции (фактически, компилятор может либо встроить, либо не встроить функцию в разное время). Генерация кода на современных процессорах гораздо сложнее, чем на более детерминированных, которые были обычны, когда Ричи изобретал C.

Что означает это слово в C ++, так это то, что оно может иметь несколько идентичных определений и должно быть определено в каждой единице перевода, которая его использует. (Другими словами, вам нужно убедиться, что он может быть встроен.) Вы можете иметь inlineфункцию в заголовке без проблем, и функции-члены, определенные в определении класса, автоматически становятся эффективными inline.


3
+1 за историю inline.
Тамара Вийсман

11
Я почти уверен, что inlineсначала это было стандартизировано в C ++, хотя оно уже было доступно в качестве расширения поставщика в C .... да, кажется, оно было добавлено в стандарт C в C99.
Бен Фойгт

@ Бен Фойгт: Ты прав. Я впервые столкнулся с этим в C.
Дэвид Торнли

5
Также имейте в виду, что не только история неверна, но правила для inlineв C99 и позже отличаются от правил для inlineв C ++.
Альф П. Штайнбах

27

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

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

В настоящее время некоторые компиляторы очень заинтересованы в подсказке, например, g ++. И некоторые компиляторы относятся к этому менее серьезно, например Visual C ++. Но все должны соблюдать гарантии.

К сожалению, эти два значения - подсказка по оптимизации и то, что мы могли бы назвать опровергаемым определением на уровне компоновщика, - принадлежат одному и тому же ключевому слову, поскольку это означает, что вы не можете иметь одно без другого.

Также прискорбно, что inline(или лучше отдельное ключевое слово о отбрасываемом определении) не может быть применено к данным .

Потребность в сбрасываемых данных на уровне компоновщика возросла, так как модули только с заголовками стали более популярными. Например, многие вспомогательные библиотеки Boost доступны только для заголовков.

Однако для данных вы можете применить небольшую хитрость с шаблонами. Определите его в некотором шаблоне класса, предоставьте typedefпараметр шаблона void(или любой другой). Это связано с тем, что правило единого определения делает специальное исключение для шаблонов.

Примечания:
¹ inlineпеременные будут поддерживаться в C ++ 17 .


1
+1 за дополнительную информацию о inline.
Тамара Вийсман

1
« эффективно идентично » на самом деле является очень сложным условием, так как очень плохо спроектированные языки C ++ очень стараются заставить обычно компетентных и осторожных программистов писать разумный, но формально неопределенный код, который использует статические объекты - интересно, сколько членов комитета сами попадают в ловушку
любопытный парень

6

Почему бы не сделать все функции встроенными по умолчанию? Потому что это инженерный компромисс. Существует как минимум два типа «оптимизации»: ускорение программы и уменьшение размера (объема памяти) программы. Встраивание вообще ускоряет вещи. Это избавляет от накладных расходов на вызов функции, избегая выталкивания и извлечения параметров из стека. Однако это также увеличивает объем памяти программы, поскольку теперь каждый вызов функции должен заменяться полным кодом функции. Чтобы сделать вещи еще более сложными, помните, что ЦП хранит часто используемые куски памяти в кеше ЦП для сверхбыстрого доступа. Если вы сделаете образ памяти программы достаточно большим, ваша программа не сможет эффективно использовать кеш, а в худшем случае встраивание может замедлить вашу программу.


5
Но компилятор может (и делает!) Понять это намного лучше, чем программист в целом. Так что это недопустимый аргумент.
Конрад Рудольф

« теперь каждый вызов функции должен быть заменен полным кодом функции ». И встроенная функция не является функцией, в которой последовательность вызовов опущена и где код сборки функции скопирован на месте. Встроенная функция компилируется на месте, благодаря встроенному высокоуровневому промежуточному коду. Это позволяет компилятору эффективно обрабатывать тело функции как чистый макрос (чистый макрос - это тот, который не имеет особенностей макросов препроцессора C) с потенциально большим количеством доступных оптимизаций.
любопытный парень

3

Чтобы понять «inline», вам нужно понять историю и то, на что была похожа жизнь 20 (и 30) лет назад.

Мы писали код на компьютерах, у которых было мало памяти, поэтому компилятор не мог обрабатывать весь код, составляющий программу, за один раз. Компилятор также был очень медленным, так что вам не нужно было перекомпилировать код, который не изменился - более 24 часов (на компьютере, который стоит более дорогой машины), чтобы перекомпилировать весь код, было нормальным для нескольких проектов, которые я работал над.

Поэтому каждый файл кода был отдельно скомпилирован в объектные файлы. Каждый объектный файл начинался со списка всех содержащихся в нем функций вместе с «адресом» функции. У объектного файла также был список всех функций, которые он вызывал в других объектных файлах, вместе с расположением вызова.

Линкер сначала прочитать все объектные файлы и создать список всех функций , которые они экспортируются вместе с файлом они были в и там адрес. Затем он перечитает все объектные файлы, выведет их в программный файл, обновляя при этом все «внешние» вызовы функций адресом функции.

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

Компилятор видел только код в файле «.c» или «.cpp», который он компилировал вместе со всеми включенными заголовочными файлами. Поэтому он не мог выполнить какую-либо оптимизацию на основе кода в других файлах «.c» или «.cpp».

Ключевое слово «inline» позволило определить тело функции (метода) в заголовочном файле, что позволило компилятору использовать код функции при компиляции кода, который ее вызывает. Например, скажем, у вас есть класс коллекции, определенный в другом файле .cpp, этот класс будет иметь метод «isEmpty», содержащий одну строку кода, и результирующая программа получит большое ускорение, если вместо вызова функции вызов функции был заменен этой строкой.

В то время ключевое слово «inline» рассматривалось как «дешевый и простой» способ, позволяющий инкапсулировать данные, избегая при этом затрат на вызовы функций, без этого многие программисты имели бы доступ только к закрытым полям объекта. (Макросы, где гораздо хуже «встраивать» код, чем обычно в то время.)

В наши дни «компоновщики» выполняют большую часть оптимизации кода и, как правило, пишутся некой командой как компилятор. Компилятор часто просто проверяет правильность кода и «сжимает» его, оставляя большую часть задачи создания машинного кода компоновщику.


2

Давайте посмотрим, что говорится в стандарте (выделены важные части жирным шрифтом):

2. Объявление функции со встроенным спецификатором объявляет встроенную функцию. Встроенный спецификатор указывает реализации, что внутренняя замена тела функции в точке вызова должна быть предпочтительнее обычного механизма вызова функции. Реализация не требуется для выполнения этой внутренней замены в точке вызова; однако, даже если эта встроенная замена опущена, другие правила для встроенных функций все равно должны соблюдаться.

- Стандарт C ++, ISO / IEC 14882: 2003 , 7.1.2 Спецификаторы функций [dcl.fct.spec]

Итак, если вы хотите быть уверены, вы должны прочитать документацию вашего компилятора.

Встраивать все - плохая идея, так как это может привести к большому количеству дублированного машинного кода ...

Так что вы должны знать:

Нет простых ответов: вы должны поиграть с ним, чтобы увидеть, что лучше. Вы не соглашайтесь на упрощенных ответы , как «Использование Never inlineфункция» или «Всегда используйте inlineфункцию» или «Использованиеinline функции тогда и только тогда , когда функция является меньше , чем N строк кода.» Эти правила одного размера могут быть легко записаны, но они приведут к неоптимальным результатам.

- C ++ FAQ, Встроенные функции , 9.3 Улучшают ли inlineфункции производительность?


1
То, что вы говорите, верно, но не имеет значения, потому что, как программист, вы не решаете, встроить или нет. Вы вводите ключевое слово, вы можете или не можете получить свои функции встроенными. Вы не ставите ключевое слово, вы все еще можете или не можете получить их встроенными. Бессмысленно иметь ЛЮБЫЕ правила, когда вы вводите ключевое слово, а компилятор является тем, кто действительно принимает решение.
Кейт Грегори

Как это не актуально, если оно говорит точно так же, как вы? Простых ответов нет .
Тамара Вийсман

Это не актуально, потому что не отвечает на вопрос ОП. Он не спрашивает «что делает inline», он спрашивает «в чем суть». И ваш ответ просто повторяет то, что мы все уже знаем о встроенном.
Давор Адрало

@Davor: "Какой смысл?" не придерживается FAQ, поэтому я отвечаю на вопросы, поставленные в вопросе. Я говорю об этом inline, повторяя знание, поскольку ОП, кажется, упускает часть, и это основная причина, по которой он не понимает. Чтобы получить очко, ему нужно понять, что за этим стоит ...
Тамара Вийсман

Какого черта он не придерживается FAQ? Стандарт описывает синтаксис и семантику встроенного ключевого слова, а вопрос ОП заключается в том, какова цель, когда он должен использоваться, какие проблемы он должен решать? Я не понимаю, как это не соответствует FAQ.
Давор Идрало

1

Позвольте мне дать вам вескую причину для использования встроенного ключевого слова.

Во встроенной системе, такой как принтер билетов или аналогичная меньшая система. Процессор очень ограничен, и вызов функции (подготовка параметров функции в стеке, вызов, извлечение параметров из стека и возврат ответа и т. Д.) Может занять несколько мс, кроме самой функции.

Допустим, время вызова составляет около 60 мс (только для вызова, а не фактической функции), и вы должны сделать 50 итераций (циклические или итеративные вызовы в дереве).

Время для перемещения вперед и назад после вызова этой функции займет 60 * 50 = 3000 (3 секунды).

Если у вас есть память, вы обязательно сделаете inline, чтобы сэкономить 3 секунды.

Таким образом, inline в основном используются, когда вам нужна скорость выполнения. В некоторых проектах, с которыми я был связан, время вызова было больше, чем время выполнения, классическая ситуация, когда использовать inline.


А что? Вставка встроенного кода в ваш код не означает, что компилятор на самом деле встроит его, и отсутствие его там также не означает, что это не так. Это просто подсказка, которая в основном игнорируется сегодняшними компиляторами. Если компилятор посчитает полезным встроить, тогда он будет, в противном случае, не будет. Вы не имеете никакого реального контроля там. ОП уже знает это и спрашивает, цитирую: «Какой смысл?». Кто, черт возьми, дает +1 на это? Это вводит в заблуждение (подразумевает, что ключевое слово inline приводит к встраиванию) и не имеет отношения к вопросу.
Давор Адрало

2
@ Davor radralo Не нужно быть грубым. Я интерпретировал вопрос как ПОЧЕМУ я должен использовать inline? Я хотел показать, что вы можете получить код быстрее за счет памяти. Каждый компилятор может обрабатывать встроенное ключевое слово по-разному, поэтому вам нужно проверить документацию, которую я не упомянул. Поскольку вы не всегда можете позволить себе создавать дополнительные встроенные накладные расходы на память, полезно «руководить», когда его использовать. Я также не сказал, что строковые данные навязаны. Кто-то посчитал этот ответ полезным для него / нее и проголосовал за +1, пожалуйста, учтите, что другие могут иметь другой уровень опыта и найти что-то тривиальное полезное.
Макс Килланд,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.