Почему начальное выделение C ++ намного больше, чем C?

138

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

Я использую компиляторы Clang и GCC версий 7.0.1-8 и 8.3.0-6 соответственно. Моя система работает на Debian 10 (Buster) последней версии. Тесты выполняются через Valgrind Massif.

#include <stdio.h>

int main() {
    printf("Hello, world!\n");
    return 0;
}

Используемый код не меняется, но независимо от того, компилирую я как C или как C ++, он изменяет результаты теста Valgrind. Однако значения остаются неизменными для разных компиляторов. Распределение времени выполнения (пик) для программы выглядит следующим образом:

  • GCC (C): 1032 байта (1 КБ)
  • G ++ (C ++): 73744 байта (~ 74 КБ)
  • Clang (C): 1032 байта (1 КБ)
  • Clang ++ (C ++): 73744 байта (~ 74 КБ)

Для компиляции я использую следующие команды:

clang -O3 -o c-clang ./main.c
gcc -O3 -o c-gcc ./main.c
clang++ -O3 -o cpp-clang ./main.cpp
g++ -O3 -o cpp-gcc ./main.cpp

Что касается Valgrind, я использую valgrind --tool=massif --massif-out-file=m_compiler_lang ./compiler-langкаждый компилятор и язык, а затем ms_printдля отображения пиков.

Я что-то здесь делаю не так?

Реруму
источник
11
Для начала, как вы строите? Какие варианты вы используете? А как измерить? Как запустить Valgrind?
Какой-то чувак-программист
17
Если я правильно помню, современные компиляторы C ++ имеют модель исключений, в которой нет снижения производительности при вводе tryблока за счет большего объема памяти, возможно, с таблицей переходов или чем-то еще. Может быть, попробуйте скомпилировать без исключений и посмотреть, какое влияние это окажет. Изменить: на самом деле, итеративно попробуйте отключить различные функции C ++, чтобы увидеть, какое влияние это оказывает на объем памяти.
François Andrieux
3
При компиляции с помощью clang++ -xcвместо было clangтакое же распределение, что явно указывает на то, что это связано с подключенными библиотеками
Джастин
14
@bigwillydos Это действительно C ++, я не вижу какой-либо части спецификаций C ++, которые он нарушает ... Кроме потенциально включения stdio.h, а не cstdio, но это разрешено, по крайней мере, в более старой версии C ++. Как вы думаете, что в этой программе "искажено"?
Vality
4
Мне кажется подозрительным, что эти компиляторы gcc и clang генерируют точно такое же количество байтов в Cрежиме и такое же количество байтов в C++режиме. Вы сделали ошибку при расшифровке?
RonJohn

Ответы:

149

Использование кучи происходит из стандартной библиотеки C ++. Он выделяет память для использования внутренней библиотеки при запуске. Если вы не ссылаетесь на него, не должно быть никакой разницы между версиями C и C ++. С помощью GCC и Clang вы можете скомпилировать файл с помощью:

g ++ -Wl, - по необходимости main.cpp

Это укажет компоновщику не связываться с неиспользуемыми библиотеками. В вашем примере кода библиотека C ++ не используется, поэтому она не должна связываться со стандартной библиотекой C ++.

Вы также можете проверить это с помощью файла C. Если вы компилируете с помощью:

gcc main.c -lstdc ++

Использование кучи снова появится, даже если вы создали программу C.

Использование кучи, очевидно, зависит от конкретной реализации библиотеки C ++, которую вы используете. В вашем случае это библиотека GNU C ++, libstdc ++ . Другие реализации могут не выделять такой же объем памяти или они могут вообще не выделять память (по крайней мере, не при запуске). Например, библиотека LLVM C ++ ( libc ++ ) не выделяет кучу при запуске, по крайней мере, в моем Linux машина:

clang ++ -stdlib = libc ++ main.cpp

Использование кучи - это то же самое, что и полное отсутствие ссылок на нее.

(Если компиляция не удалась, вероятно, libc ++ не установлена. Имя пакета обычно содержит «libc ++» или «libcxx».)

Никос К.
источник
50
Увидев этот ответ, я первой подумал: « Если этот флаг помогает уменьшить ненужные накладные расходы, почему он не включен по умолчанию? ». Есть ли на это хороший ответ?
Nat
4
@Nat Я предполагаю, что во время разработки он медленнее компилируется. Когда вы будете готовы создать релизную сборку, вы должны включить ее. Также в нормальной / большой кодовой базе разница может быть минимальной (если вы используете много библиотеки STD и т. Д.)
DarcyThomas
24
@Nat -Wl,--as-neededФлаг удаляет библиотеки, которые вы указываете в своих -lфлагах, но фактически не используете. Так что, если вы не используете библиотеку, просто не связывайтесь с ней. Для этого вам не нужен этот флаг. Однако, если ваша система сборки добавляет слишком много библиотек, и очистить их все и связать только те, которые необходимы, потребует много работы, тогда вы можете использовать этот флаг вместо этого. Стандартная библиотека является исключением, поскольку она автоматически связана с. Так что это угловой случай.
Nikos C.
36
@Nat --as-required может иметь нежелательные побочные эффекты, он работает, проверяя, используете ли вы какой-либо символ библиотеки, и удаляет те, которые не прошли тест. НО: библиотека также может выполнять различные действия неявно, например, если у вас есть статический экземпляр C ++ в библиотеке, его конструктор будет вызван автоматически. В редких случаях необходима библиотека, которую вы явно не вызываете, но они существуют.
Норберт Ланге
3
@NikosC. Системы сборки не знают автоматически, какие символы использует ваше приложение и какие библиотеки их реализуют (зависит от компиляторов, арок, дистрибутивов и библиотек c / c ++). Получить это право довольно сложно, по крайней мере, для базовых библиотек времени выполнения. Но в редких случаях, когда вам нужна библиотека, вы должны просто использовать для нее --no-as-need и оставить --as-needed везде. Вариант использования, который я видел, - это библиотеки для трассировки / отладки (lttng) и библиотеки, которые делают что-то вроде аутентификации / подключения.
Норберт Ланге
16

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

Если вы скомпилируете свой код с помощью компилятора C или C ++, вы получите ту же сборку. Ассемблер создаст те же объекты. Разница в том, что драйвер инструментальной цепочки будет предоставлять разные входные данные компоновщику для двух разных языков: разные стартапы (C ++ требует код для выполнения конструкторов и деструкторов для объектов со статической или локальной продолжительностью хранения потока на уровне пространства имен и требует инфраструктуры для стека. фреймы для поддержки раскручивания во время обработки исключений, например), стандартная библиотека C ++ (которая также имеет объекты статической продолжительности хранения на уровне пространства имен) и, возможно, дополнительные библиотеки времени выполнения (например, libgcc с ее инфраструктурой раскрутки стека).

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

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

Стивен М. Уэбб
источник
Тот факт, что включение обработки исключений имеет определенную цену, даже если вы фактически не используете ее, является проблемой. Это не нормально для функций C ++ в целом, и рабочие группы по стандартам C ++ пытаются придумать способы исправить это. См. Основной доклад Херба Саттера на ACCU 2019 De-fragmenting C ++: Making exceptions более доступные и удобные для использования . Однако в нынешнем C ++ это досадный факт. И традиционные исключения C ++, вероятно, всегда будут иметь эту стоимость, даже если / когда новые механизмы для новых исключений будут добавлены с ключевым словом.
Питер Кордес