Bagaimana cara mendeteksi file #include yang tidak perlu dalam proyek C ++ besar?

96

Saya sedang mengerjakan proyek C ++ besar di Visual Studio 2008, dan ada banyak file dengan #includearahan yang tidak perlu . Kadang-kadang #includes hanyalah artefak dan semuanya akan dikompilasi dengan baik dengan mereka dihapus, dan dalam kasus lain kelas dapat dideklarasikan dan #include dapat dipindahkan ke .cppfile. Apakah ada alat yang bagus untuk mendeteksi kedua kasus ini?

kusam
sumber

Jawaban:

50

Meskipun tidak akan mengungkapkan file include yang tidak diperlukan, Visual studio memiliki pengaturan /showIncludes(klik kanan pada .cppfile, Properties->C/C++->Advanced) yang akan menampilkan pohon dari semua file yang disertakan pada waktu kompilasi. Ini dapat membantu dalam mengidentifikasi file yang tidak perlu disertakan.

Anda juga dapat melihat idiom pimpl untuk membiarkan Anda pergi dengan lebih sedikit dependensi file header untuk membuatnya lebih mudah melihat cruft yang dapat Anda hapus.

Gerhana
sumber
1
/ showincludes sangat bagus. Melakukan ini secara manual menakutkan tanpa itu.
Shambolic
30

PC Lint bekerja cukup baik untuk ini, dan menemukan semua jenis masalah konyol lainnya untuk Anda juga. Ini memiliki opsi baris perintah yang dapat digunakan untuk membuat Alat Eksternal di Visual Studio, tetapi saya telah menemukan bahwa addin Visual Lint lebih mudah untuk digunakan. Bahkan versi gratis Visual Lint membantu. Tapi coba PC-Lint. Mengkonfigurasinya agar tidak memberi Anda terlalu banyak peringatan membutuhkan sedikit waktu, tetapi Anda akan kagum dengan hasilnya.

Joe
sumber
3
Beberapa petunjuk tentang cara melakukan ini dengan pc-lint dapat ditemukan di riverblade.co.uk/…
David Sykes
26

!!PENOLAKAN!! Saya bekerja pada alat analisis statis komersial (bukan PC Lint). !!PENOLAKAN!!

Ada beberapa masalah dengan pendekatan non parsing sederhana:

1) Set Kelebihan Beban:

Ada kemungkinan bahwa fungsi yang kelebihan beban memiliki deklarasi yang berasal dari file berbeda. Mungkin menghapus satu file header menghasilkan kelebihan muatan yang berbeda yang dipilih daripada kesalahan kompilasi! Hasilnya adalah perubahan diam-diam dalam semantik yang mungkin sangat sulit dilacak setelahnya.

2) Spesialisasi template:

Mirip dengan contoh overload, jika Anda memiliki spesialisasi parsial atau eksplisit untuk template, Anda ingin semuanya terlihat saat template digunakan. Mungkin spesialisasi untuk template utama ada di file header yang berbeda. Menghapus header dengan spesialisasi tidak akan menyebabkan kesalahan kompilasi, tetapi dapat mengakibatkan perilaku tidak terdefinisi jika spesialisasi tersebut telah dipilih. (Lihat: Visibilitas spesialisasi template dari fungsi C ++ )

Seperti yang ditunjukkan oleh 'msalters', melakukan analisis lengkap kode juga memungkinkan analisis penggunaan kelas. Dengan memeriksa bagaimana sebuah kelas digunakan melalui jalur file tertentu, dimungkinkan bahwa definisi kelas (dan oleh karena itu semua dependneciesnya) dapat dihapus seluruhnya atau setidaknya dipindahkan ke level yang lebih dekat ke sumber utama di include pohon.

Richard Corden
sumber
@RichardCorden: Perangkat lunak Anda (QA C ++) terlalu mahal.
Xander Tulip
13
@ XanderTulip: Sulit untuk menanggapi ini tanpa berakhir di promosi penjualan - jadi saya minta maaf sebelumnya. IMHO, yang harus Anda pertimbangkan adalah berapa lama waktu yang dibutuhkan seorang insinyur yang baik untuk menemukan hal-hal seperti ini (serta banyak bug bahasa / aliran kontrol lainnya) dalam proyek berukuran wajar. Saat perangkat lunak berubah, tugas yang sama perlu diulangi lagi dan lagi. Jadi, ketika Anda menghitung jumlah waktu yang dihemat maka biaya alat tersebut mungkin tidak signifikan.
Richard Corden
10

Saya tidak tahu alat semacam itu, dan saya pernah berpikir untuk menulisnya di masa lalu, tetapi ternyata ini adalah masalah yang sulit dipecahkan.

Katakanlah file sumber Anda menyertakan ah dan bh; ah berisi #define USE_FEATURE_Xdan penggunaan bh #ifdef USE_FEATURE_X. Jika #include "a.h"diberi komentar, file Anda mungkin masih dikompilasi, tetapi mungkin tidak melakukan apa yang Anda harapkan. Mendeteksi ini secara terprogram bukanlah hal yang sepele.

Alat apa pun yang melakukan ini perlu mengetahui lingkungan build Anda juga. Jika ah terlihat seperti:

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

Kemudian USE_FEATURE_Xhanya ditentukan jika WINNTditentukan, jadi alat tersebut perlu mengetahui arahan apa yang dihasilkan oleh kompilator itu sendiri serta yang mana yang ditentukan dalam perintah kompilasi daripada di file header.

Graeme Perrow
sumber
9

Seperti Timmermans, saya tidak terbiasa dengan alat apa pun untuk ini. Tetapi saya telah mengenal programmer yang menulis skrip Perl (atau Python) untuk mencoba mengomentari setiap baris yang disertakan satu per satu dan kemudian mengkompilasi setiap file.


Tampaknya sekarang Eric Raymond memiliki alat untuk ini .

Google cpplint.py memiliki "termasuk apa yang Anda gunakan" aturan (di antara banyak lainnya), tetapi sejauh yang saya tahu, tidak ada "termasuk hanya apa yang Anda gunakan." Meski begitu, bisa bermanfaat.

Max Lybbert
sumber
Saya harus tertawa ketika membaca yang ini. Bos saya melakukan hal ini pada salah satu proyek kami bulan lalu. Header yang dikurangi termasuk oleh beberapa faktor.
Don Wakefield
2
codewarrior di mac dulu memiliki skrip bawaan untuk melakukan ini, berkomentar, mengompilasi, jika ada kesalahan hapus komentar, lanjutkan ke akhir #includes. Ini hanya berfungsi untuk #includes di bagian atas file, tetapi biasanya di situlah tempatnya. Itu tidak sempurna, tetapi itu membuat semuanya tetap waras.
slycrel
5

Jika Anda tertarik dengan topik ini secara umum, Anda mungkin ingin melihat Desain Perangkat Lunak C ++ Skala Besar Lakos . Agak ketinggalan jaman, tetapi mengalami banyak masalah "desain fisik" seperti menemukan header minimum absolut yang perlu disertakan. Saya belum pernah melihat hal semacam ini didiskusikan di tempat lain.

Adrian
sumber
4

Berikan Sertakan Manajer mencoba. Ini terintegrasi dengan mudah dalam Visual Studio dan memvisualisasikan jalur penyertaan Anda yang membantu Anda menemukan hal-hal yang tidak perlu. Secara internal menggunakan Graphviz tetapi ada banyak fitur keren lainnya. Dan meskipun itu adalah produk komersial, harganya sangat rendah.

Alex
sumber
3

Jika file header Anda biasanya dimulai dengan

#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#endif

(sebagai lawan menggunakan #pragma sekali) Anda dapat mengubahnya menjadi:

#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#else 
#pragma message("Someheader.h superfluously included")
#endif

Dan karena compiler mengeluarkan nama file cpp yang sedang dikompilasi, itu akan memberi tahu Anda setidaknya file cpp mana yang menyebabkan header dibawa beberapa kali.

Sam
sumber
12
Menurut saya tidak masalah untuk menyertakan tajuk beberapa kali. Sebaiknya menyertakan apa yang Anda gunakan, dan tidak bergantung pada file penyertaan Anda untuk melakukannya. Saya pikir yang diinginkan OP adalah menemukan #includes yang sebenarnya tidak digunakan.
Ryan Ginstrom
12
IMO aktif melakukan hal yang salah. Header harus menyertakan header lain jika tidak akan berfungsi tanpanya. Dan ketika Anda memiliki A.hdan B.hkeduanya bergantung C.hdan Anda memasukkan A.hdan B.h, karena Anda membutuhkan keduanya, Anda akan memasukkan C.hdua kali, tapi tidak apa-apa, karena kompilator akan melewatkannya untuk kedua kalinya dan jika tidak, Anda harus ingat untuk selalu menyertakan C.hsebelum A.hatau B.hberakhir di inklusi yang jauh lebih tidak berguna.
Jan Hudec
5
Konten akurat, ini adalah solusi yang baik untuk menemukan tajuk yang disertakan beberapa kali. Namun, pertanyaan asli tidak terjawab oleh ini dan saya tidak dapat membayangkan kapan ini akan menjadi ide yang bagus. File Cpp harus menyertakan semua header yang mereka andalkan, bahkan jika header disertakan sebelumnya di tempat lain. Anda tidak ingin proyek Anda menjadi pesanan kompilasi khusus atau menganggap tajuk yang berbeda akan menyertakan yang Anda butuhkan.
jaypb
3

PC-Lint memang bisa melakukan ini. Salah satu cara mudah untuk melakukan ini adalah dengan mengkonfigurasinya untuk mendeteksi file include yang tidak digunakan dan mengabaikan semua masalah lainnya. Ini cukup mudah - untuk mengaktifkan hanya message 766 ("File header tidak digunakan dalam modul"), cukup sertakan opsi -w0 + e766 pada baris perintah.

Pendekatan yang sama juga dapat digunakan dengan pesan terkait seperti 964 ("File header tidak langsung digunakan dalam modul") dan 966 ("File header yang disertakan tidak langsung tidak digunakan dalam modul").

FWIW Saya menulis tentang ini secara lebih rinci dalam posting blog minggu lalu di http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318 .


sumber
2

Jika Anda ingin menghapus #includefile yang tidak perlu untuk mengurangi waktu build, waktu dan uang Anda mungkin lebih baik dihabiskan untuk memparalelkan proses build Anda menggunakan cl.exe / MP , make -j , Xoreax IncrediBuild , distcc / icecream , dll.

Tentu saja, jika Anda sudah memiliki proses build paralel dan Anda masih mencoba untuk mempercepatnya, bersihkan #includearahan Anda dan hapus dependensi yang tidak perlu tersebut.

bk1e
sumber
2

Mulailah dengan setiap file include, dan pastikan bahwa setiap file include hanya menyertakan apa yang diperlukan untuk mengkompilasi sendiri. Semua file yang disertakan yang kemudian hilang untuk file C ++, dapat ditambahkan ke file C ++ itu sendiri.

Untuk setiap penyertaan dan file sumber, beri komentar setiap file penyertaan satu per satu dan lihat apakah itu terkompilasi.

Ini juga merupakan ide yang baik untuk mengurutkan file yang disertakan menurut abjad, dan jika tidak memungkinkan, tambahkan komentar.

selwyn
sumber
2
Saya tidak yakin seberapa praktis komentar ini, jika melibatkan banyak file implementasi.
Sonny
1

Menambahkan salah satu atau kedua #defines berikut akan sering mengecualikan file header yang tidak diperlukan dan dapat meningkatkan waktu kompilasi secara substansial terutama jika kode yang tidak menggunakan fungsi Windows API.

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

Lihat http://support.microsoft.com/kb/166474

Roger Nelson
sumber
1
Tidak perlu keduanya - VC_EXTRALEAN mendefinisikan WIN32_LEAN_AND_MEAN
Aidan Ryan
1

Jika Anda belum melakukannya, menggunakan tajuk yang telah dikompilasi untuk menyertakan semua yang tidak akan Anda ubah (tajuk platform, tajuk SDK eksternal, atau bagian proyek Anda yang sudah selesai statis) akan membuat perbedaan besar dalam waktu pembuatan.

http://msdn.microsoft.com/en-us/library/szfdksca(VS.71).aspx

Juga, meskipun mungkin sudah terlambat untuk proyek Anda, mengatur proyek Anda menjadi beberapa bagian dan tidak menggabungkan semua tajuk lokal ke satu tajuk utama yang besar adalah praktik yang baik, meskipun membutuhkan sedikit kerja ekstra.

anon6439
sumber
Penjelasan bagus tentang header yang dikompilasi sebelumnya: cygnus-software.com/papers/precompiledheaders.html (Tidak yakin apakah header yang dikompilasi otomatis rusak dalam versi terbaru VisualStudio, tetapi perlu diperiksa.)
idbrii
1

Jika Anda akan bekerja dengan Eclipse CDT Anda dapat mencoba http://includator.com untuk mengoptimalkan struktur penyertaan Anda. Namun, Includator mungkin tidak cukup tahu tentang penyertaan VC ++ yang telah ditentukan sebelumnya dan menyiapkan CDT untuk menggunakan VC ++ dengan penyertaan yang benar belum dibangun ke dalam CDT.

PeterSom
sumber
1

Jetbrains IDE terbaru, CLion, secara otomatis menampilkan (dalam warna abu-abu) termasuk yang tidak digunakan dalam file saat ini.

Dimungkinkan juga untuk memiliki daftar semua include yang tidak digunakan (dan juga fungsi, metode, dll ...) dari IDE.

Jean-Michaël Celerier
sumber
0

Beberapa jawaban yang ada menyatakan itu sulit. Itu memang benar, karena Anda memerlukan kompiler lengkap untuk mendeteksi kasus-kasus di mana deklarasi penerusan akan sesuai. Anda tidak dapat mengurai C ++ tanpa mengetahui arti simbol; tata bahasanya terlalu ambigu untuk itu. Anda harus tahu apakah nama tertentu menamai kelas (bisa dideklarasikan ke depan) atau variabel (tidak bisa). Selain itu, Anda harus peka namespace.

MSalters
sumber
Anda bisa saja mengatakan "Memutuskan #termasuk mana yang diperlukan sama dengan memecahkan masalah terputus-putus. Semoga berhasil :)" Tentu saja, Anda dapat menggunakan heuristik, tetapi saya tidak tahu ada perangkat lunak gratis yang melakukan ini.
Porges
0

Jika ada header tertentu yang menurut Anda tidak diperlukan lagi (katakanlah string.h), Anda dapat mengomentari include tersebut lalu meletakkan ini di bawah semua include:

#ifdef _STRING_H_
#  error string.h is included indirectly
#endif

Tentu saja header antarmuka Anda mungkin menggunakan konvensi #define yang berbeda untuk mencatat penyertaannya dalam memori CPP. Atau tanpa konvensi, dalam hal ini pendekatan ini tidak akan berhasil.

Kemudian bangun kembali. Ada tiga kemungkinan:

  • Itu dibangun dengan baik. string.h tidak bersifat compile-critical, dan sertakannya dapat dihapus.

  • Perjalanan #error. string.g dimasukkan secara tidak langsung entah bagaimana Anda masih tidak tahu apakah string.h diperlukan. Jika diperlukan, Anda harus langsung #memasukkannya (lihat di bawah).

  • Anda mendapatkan kesalahan kompilasi lainnya. string.h diperlukan dan tidak disertakan secara tidak langsung, jadi penyertaan itu benar untuk memulai.

Perhatikan bahwa bergantung pada penyertaan tidak langsung ketika .h atau .c Anda secara langsung menggunakan .h lain hampir pasti merupakan bug: Anda sebenarnya menjanjikan bahwa kode Anda hanya akan memerlukan header itu selama beberapa header lain yang Anda gunakan memerlukannya, yang mungkin bukan yang Anda maksud.

Peringatan yang disebutkan dalam jawaban lain tentang header yang mengubah perilaku, bukan yang menyatakan hal-hal yang menyebabkan kegagalan build, juga berlaku di sini.

Britton Kerin
sumber