Dlaczego początkowa alokacja w C ++ jest o wiele większa niż w C?


138

Korzystając z tego samego kodu, po prostu zmiana kompilatora (z kompilatora C na kompilator C ++) zmieni ilość przydzielonej pamięci. Nie jestem do końca pewien, dlaczego tak jest i chciałbym to lepiej zrozumieć. Jak dotąd najlepszą odpowiedzią, jaką otrzymałem, jest „prawdopodobnie strumienie I / O”, co nie jest zbyt opisowe i sprawia, że ​​zastanawiam się nad aspektem C ++ „nie płacisz za to, czego nie używasz”.

Używam kompilatorów Clang i GCC, odpowiednio wersje 7.0.1-8 i 8.3.0-6. Mój system działa na Debianie 10 (Buster), najnowszym. Testy porównawcze są wykonywane za pośrednictwem Valgrind Massif.

#include <stdio.h>

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

Użyty kod się nie zmienia, ale niezależnie od tego, czy kompiluję jako C czy C ++, zmienia wyniki testu porównawczego Valgrind. Wartości pozostają jednak spójne we wszystkich kompilatorach. Przydziały czasu działania (szczyt) dla programu wyglądają następująco:

  • GCC (C): 1032 bajtów (1 KB)
  • G ++ (C ++): 73744 bajty, (~ 74 KB)
  • Clang (C): 1032 bajtów (1 KB)
  • Clang ++ (C ++): 73744 bajty (~ 74 KB)

Do kompilacji używam następujących poleceń:

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

W przypadku Valgrind uruchamiam valgrind --tool=massif --massif-out-file=m_compiler_lang ./compiler-langkażdy kompilator i język, a następnie ms_printwyświetlam szczyty.

Czy ja tu robię coś złego?


11
Na początek, jak budujesz? Z jakich opcji korzystasz? A jak mierzysz? Jak prowadzisz Valgrind?
— Jakiś programista koleś

17
Jeśli dobrze pamiętam, współczesne kompilatory C ++ muszą stosować model wyjątków, w którym nie ma wydajności do wejścia do trybloku kosztem większej ilości pamięci, być może z tabelą skoków lub czymś podobnym. Może spróbuj kompilować bez wyjątków i zobacz, jaki to ma wpływ. Edycja: W rzeczywistości spróbuj iteracyjnie wyłączyć różne funkcje C ++, aby zobaczyć, jaki wpływ ma to na zużycie pamięci.
— François Andrieux

3
Podczas kompilacji z clang++ -xczamiast clang, był tam ten sam przydział, co zdecydowanie sugeruje, że jest to spowodowane powiązanymi bibliotekami
— Justin

14
@bigwillydos To jest rzeczywiście C ++, nie widzę żadnej części specyfikacji C ++, którą łamie ... Poza tym, że potencjalnie zawiera stdio.h zamiast cstdio, ale jest to dozwolone przynajmniej w starszej wersji C ++. Jak myślisz, co jest „zniekształcone” w tym programie?
— Vality

4
Wydaje mi się podejrzane, że te kompilatory gcc i clang generują dokładnie taką samą liczbę bajtów w Ctrybie i dokładnie taką samą liczbę bajtów w C++trybie. Czy popełniłeś błąd w transkrypcji?
— RonJohn

Odpowiedzi:


149

Użycie sterty pochodzi ze standardowej biblioteki C ++. Przydziela pamięć do użytku wewnętrznej biblioteki podczas uruchamiania. Jeśli nie utworzysz linku przeciwko temu, różnica między wersjami C i C ++ powinna wynosić zero. Dzięki GCC i Clang możesz skompilować plik za pomocą:

g ++ -Wl, - w razie potrzeby main.cpp

Poinstruuje to konsolidator, aby nie łączył się z nieużywanymi bibliotekami. W przykładowym kodzie biblioteka C ++ nie jest używana, więc nie powinna być połączona z biblioteką standardową C ++.

Możesz to również przetestować z plikiem C. Jeśli kompilujesz z:

gcc main.c -lstdc ++

Użycie sterty pojawi się ponownie, nawet jeśli utworzyłeś program w C.

Użycie sterty zależy oczywiście od konkretnej implementacji biblioteki C ++, której używasz. W twoim przypadku jest to biblioteka GNU C ++, libstdc ++ . Inne implementacje mogą nie przydzielać takiej samej ilości pamięci lub mogą nie przydzielać żadnej pamięci w ogóle (przynajmniej nie przy uruchamianiu). Na przykład biblioteka LLVM C ++ ( libc ++ ) nie przydziela sterty przy uruchamianiu, przynajmniej w moim Linuksie maszyna:

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

Użycie sterty jest tym samym, co brak łączenia z nią w ogóle.

(Jeśli kompilacja się nie powiedzie, prawdopodobnie libc ++ nie jest zainstalowane. Nazwa pakietu zwykle zawiera „libc ++” lub „libcxx”).


50
Widząc tę ​​odpowiedź, pierwszą myślą jest: „ Jeśli ta flaga pomaga zredukować niepotrzebne obciążenie, dlaczego nie jest włączona domyślnie? ”. Czy jest na to dobra odpowiedź?
— Nat,

4
@Nat Domyślam się, że w czasie programowania kompilacja jest wolniejsza. Gdy jesteś gotowy do utworzenia wersji wydania, możesz ją włączyć. Również w normalnej / dużej bazie kodu różnica może być minimalna (jeśli używasz wielu bibliotek STD itp.)
— DarcyThomas

24
@Nat -Wl,--as-neededFlaga usuwa biblioteki określone we -lflagach, których w rzeczywistości nie używasz. Więc jeśli nie korzystasz z biblioteki, po prostu nie umieszczaj na niej linków. Nie potrzebujesz do tego tej flagi. Jeśli jednak twój system kompilacji dodaje zbyt wiele bibliotek i wyczyszczenie ich wszystkich i powiązanie tylko tych potrzebnych byłoby dużo pracy, możesz zamiast tego użyć tej flagi. Biblioteka standardowa jest jednak wyjątkiem, ponieważ jest automatycznie połączona z. Więc to jest przypadek narożny.
— Nikos C.

36
@Nat - w razie potrzeby może mieć niepożądane efekty uboczne, działa, sprawdzając, czy używasz dowolnego symbolu biblioteki i wyrzuca te, które nie przejdą testu. ALE: biblioteka może również wykonywać różne rzeczy niejawnie, na przykład, jeśli masz w bibliotece statyczną instancję C ++, jej konstruktor zostanie wywołany automatycznie. Są rzadkie przypadki, w których biblioteka, do której nie dzwonisz jawnie, jest konieczna, ale istnieją.
— Norbert Lange

3
@NikosC. Systemy kompilacji nie wiedzą automatycznie, jakich symboli używa twoja aplikacja i które biblioteki je implementują (różni się w zależności od kompilatorów, archiwów, dystrybucji i bibliotek c / c ++). Uzyskanie tego jest raczej kłopotliwe, przynajmniej dla podstawowych bibliotek wykonawczych. Ale w rzadkich przypadkach, gdy potrzebujesz biblioteki, powinieneś po prostu użyć - nie-w razie potrzeby do tego i pozostawić - tak jak jest potrzebne wszędzie indziej. Przykładem zastosowania, który widziałem, są biblioteki do śledzenia / debugowania (lttng) i biblioteki, które wykonują coś w rodzaju uwierzytelniania / łączenia.
— Norbert Lange

16

Ani GCC, ani Clang nie są kompilatorami - w rzeczywistości są programami sterowników narzędzi. Oznacza to, że wywołują kompilator, asembler i konsolidator.

Jeśli skompilujesz swój kod za pomocą kompilatora C lub C ++, otrzymasz ten sam wyprodukowany zestaw. Asembler wyprodukuje te same obiekty. Różnica polega na tym, że sterownik toolchain będzie dostarczał różne dane wejściowe do konsolidatora dla dwóch różnych języków: różnych startów (C ++ wymaga kodu do wykonywania konstruktorów i destruktorów dla obiektów ze statycznym lub lokalnym wątkiem czasu trwania na poziomie przestrzeni nazw i wymaga infrastruktury dla stosu ramki do obsługi rozwijania podczas przetwarzania wyjątków), standardową bibliotekę C ++ (która również zawiera obiekty o statycznym czasie trwania na poziomie przestrzeni nazw) i prawdopodobnie dodatkowe biblioteki uruchomieniowe (na przykład libgcc z infrastrukturą rozwijania stosu).

Krótko mówiąc, to nie kompilator powoduje zwiększenie rozmiaru, ale linkowanie rzeczy, które wybrałeś, wybierając język C ++.

To prawda, że ​​C ++ ma filozofię „płać tylko za to, czego używasz”, ale używając języka, płacisz za to. Możesz wyłączyć części języka (RTTI, obsługa wyjątków), ale wtedy nie używasz już C ++. Jak wspomniano w innej odpowiedzi, jeśli w ogóle nie korzystasz z biblioteki standardowej, możesz poinstruować sterownik, aby to pominął (--Wl, - w razie potrzeby), ale jeśli nie zamierzasz używać żadnej z funkcji C ++ lub jego biblioteki, dlaczego w ogóle wybierasz C ++ jako język programowania?


Fakt, że włączenie obsługi wyjątków kosztuje, nawet jeśli w rzeczywistości go nie używasz, jest problemem. To nie jest normalne w przypadku funkcji C ++ w ogóle i jest to coś, co grupy robocze ds. Standardów C ++ próbują wymyślić sposoby naprawienia. Zobacz przemówienie Herba Suttera na ACCU 2019 De-fragmenting C ++: Making wyjątki bardziej przystępne i użyteczne . Jest to jednak niefortunny fakt w obecnym C ++. Tradycyjne wyjątki C ++ będą prawdopodobnie zawsze miały ten koszt, nawet jeśli / kiedy nowe mechanizmy nowych wyjątków zostaną dodane za pomocą słowa kluczowego.
— Peter Cordes
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.