Mengapa alokasi awal C ++ jauh lebih besar daripada C?

138

Saat menggunakan kode yang sama, mengubah kompiler (dari kompilator C ke kompilator C ++) akan mengubah berapa banyak memori yang dialokasikan. Saya tidak begitu yakin mengapa ini terjadi dan ingin lebih memahaminya. Sejauh ini tanggapan terbaik yang saya dapatkan adalah "mungkin aliran I / O", yang tidak terlalu deskriptif dan membuat saya bertanya-tanya tentang aspek "Anda tidak membayar untuk apa yang tidak Anda gunakan" dari C ++.

Saya menggunakan kompiler Clang dan GCC, masing-masing versi 7.0.1-8 dan 8.3.0-6. Sistem saya berjalan di Debian 10 (Buster), terbaru. Tolok ukur dilakukan melalui Valgrind Massif.

#include <stdio.h>

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

Kode yang digunakan tidak berubah, tetapi apakah saya mengkompilasi sebagai C atau sebagai C ++, itu mengubah hasil benchmark Valgrind. Namun, nilainya tetap konsisten di seluruh penyusun. Alokasi runtime (puncak) untuk program berjalan sebagai berikut:

  • GCC (C): 1.032 byte (1 KB)
  • G ++ (C ++): 73.744 byte, (~ 74 KB)
  • Dentang (C): 1.032 byte (1 KB)
  • Clang ++ (C ++): 73.744 byte (~ 74 KB)

Untuk kompilasi, saya menggunakan perintah berikut:

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

Untuk Valgrind, saya menjalankan valgrind --tool=massif --massif-out-file=m_compiler_lang ./compiler-langsetiap compiler dan bahasa, lalu ms_printuntuk menampilkan puncaknya.

Apakah saya melakukan sesuatu yang salah di sini?

Rerumu
sumber
11
Pertama-tama, bagaimana Anda membangun? Opsi apa yang Anda gunakan? Dan bagaimana Anda mengukurnya? Bagaimana Anda menjalankan Valgrind?
Beberapa programmer,
17
Jika saya ingat dengan benar, kompiler C ++ modern harus model pengecualian di mana tidak ada kinerja yang terpukul untuk memasuki tryblok dengan mengorbankan jejak memori yang lebih besar, mungkin dengan tabel lompat atau sesuatu. Mungkin coba kompilasi tanpa pengecualian dan lihat apa dampaknya. Sunting: Nyatanya, coba nonaktifkan berbagai fitur c ++ secara berulang untuk melihat dampaknya pada footprint memori.
François Andrieux
3
Saat mengompilasi dengan clang++ -xcalih - alih clang, alokasi yang sama ada di sana, yang sangat menyarankan itu karena perpustakaan tertaut
Justin
14
@bigwillydos Ini memang C ++, saya tidak melihat bagian apa pun dari spesifikasi C ++ yang rusak ... Selain berpotensi menyertakan stdio.h daripada cstdio tetapi ini diizinkan setidaknya dalam versi C ++ yang lebih lama. Menurut Anda, apa yang "salah format" dalam program ini?
Vality
4
Saya merasa curiga bahwa kompiler gcc dan clang tersebut menghasilkan jumlah byte yang sama persis dalam Cmode dan jumlah C++mode byte yang sama persis . Apakah Anda membuat kesalahan transkripsi?
RonJohn

Jawaban:

149

Penggunaan heap berasal dari pustaka standar C ++. Ini mengalokasikan memori untuk penggunaan perpustakaan internal saat startup. Jika Anda tidak menautkannya, seharusnya tidak ada perbedaan antara versi C dan C ++. Dengan GCC dan Clang, Anda dapat mengompilasi file dengan:

g ++ -Wl, - sesuai kebutuhan main.cpp

Ini akan menginstruksikan penaut untuk tidak menautkan ke pustaka yang tidak digunakan. Dalam kode contoh Anda, pustaka C ++ tidak digunakan, jadi seharusnya tidak ditautkan ke pustaka standar C ++.

Anda juga dapat menguji ini dengan file C. Jika Anda mengkompilasi dengan:

gcc main.c -lstdc ++

Penggunaan heap akan muncul kembali, meskipun Anda telah membuat program C.

Penggunaan heap jelas bergantung pada implementasi library C ++ spesifik yang Anda gunakan. Dalam kasus Anda, itulah pustaka GNU C ++, libstdc ++ . Implementasi lain mungkin tidak mengalokasikan jumlah memori yang sama, atau mereka mungkin tidak mengalokasikan memori sama sekali (setidaknya tidak saat startup.) Perpustakaan LLVM C ++ ( libc ++ ) misalnya tidak melakukan alokasi heap saat startup, setidaknya di Linux saya mesin:

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

Penggunaan heap sama dengan tidak menautkan sama sekali.

(Jika kompilasi gagal, libc ++ mungkin tidak diinstal. Nama paket biasanya berisi "libc ++" atau "libcxx".)

Nikos C.
sumber
50
Saat melihat jawaban ini, pikiran pertama saya adalah, " Jika flag ini membantu mengurangi overhead yang tidak diperlukan, mengapa tidak diaktifkan secara default? ". Apakah ada jawaban yang bagus untuk itu?
Nat
4
@Nat Dugaan saya adalah pada waktu pengembang kompilasi lebih lambat. Saat Anda siap untuk membuat rilis build, Anda harus mengaktifkannya. Juga dalam basis kode normal / besar perbedaannya mungkin minimal (jika Anda menggunakan banyak pustaka STD, dll.)
DarcyThomas
24
@Nat -Wl,--as-neededBendera menghapus pustaka yang Anda tentukan di -lbendera Anda tetapi sebenarnya tidak Anda gunakan. Jadi jika Anda tidak menggunakan perpustakaan, jangan tautkan ke perpustakaan itu. Anda tidak membutuhkan bendera ini untuk ini. Namun, jika sistem build Anda menambahkan terlalu banyak pustaka dan akan merepotkan untuk membersihkan semuanya dan hanya menautkan yang diperlukan, Anda dapat menggunakan tanda ini sebagai gantinya. Pustaka standar adalah pengecualian, karena itu secara otomatis ditautkan. Jadi ini adalah kasus sudut.
Nikos C.
36
@Nat --as-required dapat memiliki efek samping yang tidak diinginkan, ini bekerja dengan memeriksa apakah Anda menggunakan simbol library dan menendang yang gagal dalam pengujian. TAPI: perpustakaan juga dapat melakukan berbagai hal secara implisit, misalnya, jika Anda memiliki instance C ++ statis di perpustakaan, maka konstruktornya akan dipanggil secara otomatis. Ada kasus yang jarang terjadi di mana perpustakaan yang tidak Anda panggil secara eksplisit diperlukan, tetapi mereka ada.
Norbert Lange
3
@Nikita. Sistem build tidak secara otomatis mengetahui simbol mana yang digunakan aplikasi Anda, dan library mana yang menerapkannya (bervariasi antara compiler, archs, distro, dan library c / c ++). Melakukannya dengan benar agak merepotkan, setidaknya untuk pustaka waktu proses dasar. Tetapi untuk kasus yang jarang terjadi, Anda memerlukan pustaka, Anda harus menggunakan --tidak sesuai kebutuhan untuk yang satu itu, dan tinggalkan --seperti diperlukan di tempat lain. Kasus penggunaan yang saya lihat adalah pustaka untuk penelusuran / debugging (lttng) dan pustaka yang melakukan semacam otentikasi / penghubung.
Norbert Lange
16

Baik GCC maupun Clang bukanlah kompiler - mereka sebenarnya adalah program driver toolchain. Itu berarti mereka memanggil compiler, assembler, dan linker.

Jika Anda mengkompilasi kode Anda dengan kompiler C atau C ++, Anda akan mendapatkan perakitan yang sama. Assembler akan menghasilkan objek yang sama. Perbedaannya adalah driver toolchain akan memberikan masukan yang berbeda ke linker untuk dua bahasa yang berbeda: startup yang berbeda (C ++ memerlukan kode untuk mengeksekusi konstruktor dan destruktor untuk objek dengan durasi penyimpanan statis atau thread-lokal pada tingkat namespace, dan memerlukan infrastruktur untuk stack frame untuk mendukung pelepasan selama pemrosesan pengecualian, misalnya), pustaka standar C ++ (yang juga memiliki objek dengan durasi penyimpanan statis pada tingkat ruang nama), dan mungkin pustaka waktu proses tambahan (misalnya, libgcc dengan infrastruktur pelepasan tumpukannya).

Singkatnya, ini bukan kompiler yang menyebabkan peningkatan footprint, ini adalah penautan hal-hal yang Anda pilih untuk digunakan dengan memilih bahasa C ++.

Memang benar bahwa C ++ memiliki filosofi "bayar hanya untuk apa yang Anda gunakan", tetapi dengan menggunakan bahasanya, Anda membayarnya. Anda dapat menonaktifkan bagian bahasa (RTTI, penanganan pengecualian) tetapi kemudian Anda tidak menggunakan C ++ lagi. Seperti yang disebutkan dalam jawaban lain, jika Anda tidak menggunakan pustaka standar sama sekali, Anda dapat menginstruksikan pengemudi untuk membiarkannya (--Wl, - sesuai kebutuhan) tetapi jika Anda tidak akan menggunakan fitur apa pun C ++ atau librarynya, mengapa Anda memilih C ++ sebagai bahasa pemrograman?

Stephen M. Webb
sumber
Fakta bahwa mengaktifkan penanganan pengecualian memiliki biaya meskipun Anda tidak benar-benar menggunakannya merupakan masalah. Itu tidak normal untuk fitur C ++ secara umum, dan itu adalah sesuatu yang coba dipikirkan oleh kelompok kerja standar C ++ untuk memperbaikinya. Lihat ceramah utama Herb Sutter di ACCU 2019 De-fragmenting C ++: Membuat pengecualian lebih terjangkau dan dapat digunakan . Ini adalah fakta yang disayangkan, dalam C ++ saat ini. Dan pengecualian C ++ tradisional mungkin akan selalu memiliki biaya tersebut, bahkan jika / ketika mekanisme baru untuk pengecualian baru ditambahkan dengan kata kunci.
Peter Cordes