Значительно ли увеличивает занимаемую площадь использование STL?

Значительно ли увеличивает занимаемую площадь использование STL? Не могли бы вы поделиться своим опытом по этому поводу? Каковы лучшие практики для создания небольшой библиотеки?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
13
0
2 480
8

Ответы 8

Однозначного ответа нет, поскольку STL - это набор шаблонов. Шаблоны по самой своей природе компилируются только при использовании. Таким образом, вы можете включить весь STL, и если он фактически не используется, размер, добавленный STL, будет равен нулю. Если у вас очень маленькое приложение, которое может использовать множество различных шаблонов с разной специализацией, занимаемая площадь может быть большой.

Очень хороший ответ. Если пойти дальше, решением STL может быть даже меньше, если код, написанный для исключения использования STL, на самом деле не используется.

John D. Cook 15.12.2008 06:56

Во встроенном проекте с лимитом 64 КБ, который я когда-то делал, я даже не мог связать стандартные библиотеки C. Так что это зависит от того, что вам нужно делать

Использование STL увеличит ваш двоичный размер по двум основным причинам:

Встраивание

По умолчанию компилятор обрабатывает код шаблона как встроенный. Следовательно, если вы используете std::list<int> в нескольких разных модулях компиляции, и компилятор встраивает этот код, каждый из них будет иметь свои собственные локальные встроенные определения функций std::list<int> (что, вероятно, не имеет большого значения, поскольку по умолчанию компилятор будет встроить только очень маленькие определения).

Обратите внимание, что (как указывает Мартин в другом месте) множественно определенные символы удаляются всеми современными компоновщиками C++ на больших платформах, как описано в Документация GCC. Таким образом, если компилятор оставляет код шаблона вне очереди, компоновщик удаляет дубликаты.

Специализация

Из-за самой природы шаблонов C++ std::list<int> потенциально произвольно отличается от std::list<double>. Фактически, стандарт требует, чтобы std::vector<bool> был определен как битовый вектор, поэтому большинство операций там полностью отличается от std::vector<T> по умолчанию.

С этим может справиться только сопровождающий библиотеки. Одно из решений - взять базовую функциональность и удалить ее из шаблона. Превратите его в структуру данных в стиле C с помощью void* повсюду. Тогда интерфейс шаблона, который видят нижестоящие разработчики, представляет собой тонкую оболочку. Это уменьшает количество дублированного кода, поскольку все специализации шаблонов имеют общую основу.

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

jalf 15.12.2008 05:15

Jalf - эти проблемы относятся ко всему программному обеспечению C или C++, а не только к шаблонам. Но это тоже хороший момент.

Tom 15.12.2008 05:24

Ваш внутренний аргумент не работает. Метод помечается как встроенный, если он определен в объявлении класса. Но это все еще зависит от компилятора, действительно ли код встроен или нет. Если это значительно увеличит размер кода, он не будет встроен.

Martin York 15.12.2008 05:51

Да, std :: list <int> отличается от std :: list <double>, поэтому у вас будет две разные версии. Но как бы вы реализовали ту же функциональность в собственном коде? Вы реализуете два разных типа и заранее находитесь в таком же положении, как если бы вы использовали шаблоны.

Martin York 15.12.2008 05:54

Даже если компилятор решит не встраивать функцию шаблона, вы все равно получите копию в каждой единице компиляции. Если у вас много модулей, использующих std :: list <int>, это много дублированного кода.

Mark Ransom 15.12.2008 06:18

Мартин: да, подкладка абсолютно держится. Даже если компилятор решит, что std :: vector <int> :: push_back () должен быть вне очереди, он будет определен в каждой единице компиляции, которая его использует. Таким образом, он все еще дублируется в окончательном двоичном файле, если у вас нет сложного компоновщика.

Tom 15.12.2008 06:19

@Mark Ransom: АБСОЛЮТНО ЛОЖЬ. Да, он дублируется, но во время компоновки все, кроме одного, ДОЛЖНЫ быть удалены, иначе возникнут ошибки компоновщика. Таким образом, если он не встроен, будет только ОДНА копия каждого метода.

Martin York 15.12.2008 06:42

@Tom: Да, компоновщик сложный. Это ТРЕБОВАНИЕ для возможности связывания C++. Попробуйте сбросить символы из полученного приложения, вы найдете только ОДНУ версию каждого метода.

Martin York 15.12.2008 06:44

Пожалуйста, прочтите мой пост, чтобы объяснить, почему ваши предположения ошибочны.

Martin York 15.12.2008 07:19

@Mark - Вы на 100% правы, с GNU ld и GCC. Не все компиляторы и компоновщики делают это (быстрый поиск в Google показывает, что Sun CC на Solaris 9 плохо себя ведет). Вот обсуждение, объясняющее, почему ld не всегда может просто выбросить несколько определений: sourceware.org/ml/binutils/2002-02/msg00142.html

Tom 15.12.2008 08:07

@Tom: Опять же, не совсем правильно. Компилятор sun просто придерживается другой тактики. Вместо того, чтобы помещать определения методов шаблона в модуль компиляции, он использует каталог временного кэша. Таким образом устраняются дубликаты с помощью имен файлов для представления методов.

Martin York 15.12.2008 08:17

@Tom: Но дискуссии, которую вы цитируете, уже 6 лет, и она полностью устарела.

Martin York 15.12.2008 08:18

@Martin: Если вы можете указать мне что-то авторитетное, описывающее, как соответствующие компоновщики C++ требуются для решения этой проблемы, я вытащу обсуждение встраивания. Я хотел бы знать, что должно произойти, когда тела функций различаются.

Tom 15.12.2008 08:23

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

Mark Ransom 15.12.2008 08:42

Ответ Дэйва Делани содержит ссылку, описывающую систему «Borland» - экземпляры шаблонов помещаются в общие блоки, которые компоновщик свернет. Интересный подход, вселяет надежду, что компилятор / компоновщик умнее, чем я думал.

Mark Ransom 15.12.2008 09:21

@Tom: Все компиляторы / компоновщики будут гарантировать, что исполняемый файл действителен, только если все объекты построены с одинаковыми флагами. Это НЕ проблема с шаблоном, это общая проблема, которая затрагивает все части языка. Вот почему IDE создают разные версии (отладка / выпуск) в разных каталогах.

Martin York 15.12.2008 10:05

MSVC объединит функции, если сборка идентична. Поэтому list <int> :: push_back и list <void *> :: push_back часто объединяются, поскольку оба копируют 32 бита, не глядя.

MSalters 15.12.2008 13:58

@Martin - Я всегда считал правильным создавать единицы компиляции с разными флагами компилятора, при условии, что они бинарно совместимы. Это все еще действительно, если все тела функций определены вне очереди?

Tom 15.12.2008 17:37

@Martin - документация GNU, которую вы (и Дэйв Делани) указали, чтобы указать, что в некоторых системах GNU ld будет нет обрабатывать автоматическое создание экземпляров шаблона. Означает ли это, что явное создание экземпляра - требуется, чтобы избежать множественно определенных символов на этих платформах?

Tom 15.12.2008 17:38

@Tom: Да, проблема в двоичной совместимости. Но какие флаги это меняют. Вот почему в IDE (которые выбирают безопасный курс) каждая цель создается в отдельных каталогах. И все объекты для цели строятся с использованием одних и тех же флагов. Но это не проблема шаблона.

Martin York 15.12.2008 18:20

Я предполагаю, что вы имеете в виду след во время выполнения объем памяти и, следовательно, STL контейнеры.

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

Некоторые хорошие практики:

  • Если ваша библиотека собирается предоставлять эти контейнеры через API, вам, возможно, придется выбирать между помещением кода STL в заголовки библиотеки или отказом от использования STL. Проблема в том, что мой компилятор не должен реализовывать STL так же, как ваш.
  • Узнайте, как и когда STL контейнеры выделяют память. Когда вы можете визуализировать, как растет дек и сжимается по сравнению с вектором, вы будете лучше подготовлены к тому, чтобы решить, какой из них использовать.
  • Если вам нужно микроуправлять каждым байтом, подумайте о написании собственных распределителей. Это редко требуется за пределами встроенных систем.

Дело в STL, потому что это все шаблоны, которые он только увеличивает размер, когда вы фактически используете. Если вы не используете метод, этот метод не создается.

Но за то, что вы используете, всегда будет цена.

Но настоящий вопрос вы должны задать. Будет ли размер больше или меньше вашей собственной реализации? Если вы не используете STL, что вы используете? Вы можете написать собственное письмо от руки, но за это придется заплатить. Это не нулевое пространство, он не будет хорошо протестирован, и вы не будете следовать установленным передовым методам.

Так что на самом деле это не раздувает ваш код. Поскольку добавление эквивалентной функциональности каким-либо другим методом добавит столько же кода, это просто не будет так хорошо протестировано.

В другом сообщении говорится, что код шаблона должен быть встроен или иметь несколько определений. Это абсолютно НЕПРАВИЛЬНЫЙ.

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

Если не встроено, то копия метода будет сгенерирована в каждой единице компиляции, которая использует метод. Но ТРЕБОВАНИЕ к компоновщику C++ состоит в том, что он должен удалять ВСЕ, кроме ОДНОЙ копии этих методов, когда приложение связано с исполняемым файлом. Если компилятор не удалил лишние копии, ему пришлось бы сгенерировать ошибку компоновщика нескольких определений.

Это легко показать:
Следующее иллюстрирует это:

  • Метод _M_insert_aux () не встроен.
  • Он размещен в обоих модулях компиляции.
  • Эта только одна копия метода находится в конечном исполняемом файле.

a.cpp

#include <vector>

void a(std::vector<int>& l)
{
    l.push_back(1);
    l.at(0) = 2;
}

b.cpp

#include <vector>

void b(std::vector<int>& l)
{
    l.push_back(1);
    l.at(0) = 2;
}

main.cpp

#include <vector>

void a(std::vector<int>&);
void b(std::vector<int>&);

int main()
{
    std::vector<int>    x;
    a(x);
    b(x);
}

Проверка

>g++ -c a.cpp
>g++ -c b.cpp

>nm a.o
<removed other stuff>
000000a0 S __ZNSt6vectorIiSaIiEE13_M_insert_auxEN9__gnu_cxx17__normal_iteratorIPiS1_EERKi
<removed other stuff>

>nm b.o
<removed other stuff>
000000a0 S __ZNSt6vectorIiSaIiEE13_M_insert_auxEN9__gnu_cxx17__normal_iteratorIPiS1_EERKi
<removed other stuff>

>c++filt __ZNSt6vectorIiSaIiEE13_M_insert_auxEN9__gnu_cxx17__normal_iteratorIPiS1_EERKi
std::vector<int, std::allocator<int> >::_M_insert_aux(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, int const&)

>g++ a.o b.o main.cpp
nm a.out | grep __ZNSt6vectorIiSaIiEE13_M_insert_auxEN9__gnu_cxx17__normal_iteratorIPiS1_EERKi
00001700 T __ZNSt6vectorIiSaIiEE13_M_insert_auxEN9__gnu_cxx17__normal_iteratorIPiS1_EERKi

Я думаю, что для точного ответа на исходный вопрос нам нужно больше узнать о среде OP. Мой пост - худший случай, ваш пост - самый частый. Впрочем, поучительно и немного пугающе. Если a.cxx скомпилирован -Os, а b.cxx скомпилирован -O3, функции явно различаются.

Tom 15.12.2008 08:13

Гарантирует ли компилятор двоичную совместимость между объектами, скомпилированными с разными флагами? (Я не знаю). Это одна из причин, по которой IDE (через Targets) собирают все объекты -Os в один каталог, а все -O3 - в другой каталог и только связывают вместе объекты, созданные с использованием тех же флагов.

Martin York 16.12.2008 17:34

Игнорируя пока обсуждение STL, есть одна неочевидная важная практика для создания статической библиотеки, занимающей мало места. Разделите свою библиотеку на как можно больше разрозненных единиц компиляции. Например, если вы посмотрите на libpthread.a, вы увидите, что каждая функция имеет свой собственный блок компиляции. Многие компоновщики выбрасывают мертвый код на основе целых единиц компиляции, но не более детализированного, чем это. Если я использую только несколько функций из библиотеки pthreads, мой компоновщик внесет только эти определения и ничего больше. С другой стороны, если бы вся библиотека была скомпилирована в один объектный файл, моему компоновщику пришлось бы добавить всю библиотеку как единый «блок».

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

Хотя в нем не рассматриваются шаблоны STL, в документации по GCC есть небольшой раздел, посвященный минимизации раздувания кода при использовании шаблонов-шаблонов.

Ссылка есть http://gcc.gnu.org/onlinedocs/gcc-4.3.2/gcc/Template-Instantiation.html#Template-Instantiation

Специализации STL, основанные на указателях, могут иметь одну и ту же реализацию. Это потому, что (void *) имеет тот же размер, что и (int *) или (foo *). Так что пока:

vector 〈int〉 и vector 〈foo〉 - разные реализации.

vector 〈int *〉 и vector 〈foo *〉 могут иметь большую часть одной и той же реализации. Многие реализации STL делают это для экономии памяти.

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

Они также автоматически встраивают методы в определение класса для более высоких уровней оптимизации. Но этим можно управлять с помощью параметров компилятора.

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