Як мені виявити непотрібні файли #include у великому проекті C ++?


96

Я працюю над великим проектом C ++ у Visual Studio 2008, і є багато файлів із непотрібними #includeдирективами. Іноді #includes - це лише артефакти, і все буде скомпільовано нормально, з їх видаленням, а в інших випадках класи можуть бути оголошені вперед і #include може бути переміщений у .cppфайл. Чи є якісь хороші інструменти для виявлення обох цих випадків?

Відповіді:


50

Незважаючи на те, що він не виявить непотрібних файлів включення, Visual studio має параметр /showIncludes(клацніть правою кнопкою миші на .cppфайлі Properties->C/C++->Advanced), який буде видавати дерево всіх включених файлів під час компіляції. Це може допомогти у визначенні файлів, які не потрібно включати.

Ви також можете поглянути на ідіому pimpl, щоб дозволити вам уникнути із меншою кількістю залежностей файлів заголовків, щоб полегшити розгляд несправності, яку ви можете видалити.


1
/ showincludes - це чудово. Робити це вручну було страшно без цього.
shambolic

30

PC Lint працює досить добре для цього, і він знаходить всілякі інші дурні проблеми і для вас. У ньому є параметри командного рядка, які можна використовувати для створення зовнішніх інструментів у Visual Studio, але я виявив, що з надбудовою Visual Lint легше працювати. Навіть безкоштовна версія Visual Lint допомагає. Але дайте постріл PC-Lint. Налаштування, щоб воно не надто багато попереджало, займає трохи часу, але ви будете вражені тим, що воно з’явиться.


3
Деякі вказівки щодо того, як це зробити за допомогою pc-lint, можна знайти на сайті riverblade.co.uk/…
Девід Сайкс

29

Існує новий інструмент на базі Clang, включаючи те, що ти використовуєш , який має на меті це зробити.


Зауважте, що в даний час це не будується з
clang

4 червня 2015 р. Випущено iwyu 0.4. Він сумісний з llvm + clang 3.6 відповідно до домашньої сторінки iwyu .
Ерік Сьолунд,

26

!! ЗАМОВЛЕННЯ !! Я працюю над комерційним інструментом статичного аналізу (не PC Lint). !! ЗАМОВЛЕННЯ !!

Існує кілька проблем із простим підходом без розбору:

1) Набори для перевантаження:

Можливо, що перевантажена функція має декларації, що надходять з різних файлів. Можливо, видалення одного заголовкового файлу призводить до іншого перевантаження, а не до помилки компіляції! Результатом буде тиха зміна семантики, яку потім буде дуже важко відстежити.

2) Спеціалізації шаблонів:

Подібно прикладу перевантаження, якщо у вас є часткові або явні спеціалізації для шаблону, ви хочете, щоб усі вони були видимими, коли шаблон використовується. Можливо, спеціалізації для первинного шаблону знаходяться в різних файлах заголовків. Видалення заголовка зі спеціалізацією не призведе до помилки компіляції, але може призвести до невизначеної поведінки, якщо цю спеціалізацію було вибрано. (Див.: Видимість спеціалізації шаблону функції C ++ )

Як зазначає "msalters", проведення повного аналізу коду також дозволяє проводити аналіз використання класу. Перевіривши, як клас використовується за допомогою певного шляху до файлів, можливо, що визначення класу (і, отже, всі його залежності) може бути повністю видалено або, принаймні, перенесено на рівень, ближчий до основного джерела у include дерево.


@RichardCorden: Ваше програмне забезпечення (QA C ++) занадто дороге.
Ксандер Тюльпан

13
@XanderTulip: Важко відповісти на це, не опинившись на відстані продажів, тому я заздалегідь прошу вибачення. ІМХО, вам слід врахувати, скільки часу знадобиться хорошому інженеру, щоб знайти подібні речі (а також багато інших помилок мови / управління потоком) у будь-якому проекті розумного розміру. У міру зміни програмного забезпечення одне і те ж завдання потрібно повторювати знову і знову. Отже, коли ви визначаєте кількість заощадженого часу, тоді вартість інструменту, ймовірно, не є значною.
Річард Корден,

10

Я не знаю жодного з таких інструментів, і я думав про те, щоб написати його раніше, але виявляється, що це складна проблема для вирішення.

Скажімо, ваш вихідний файл містить ах і бх; ah містить #define USE_FEATURE_Xі bh використання #ifdef USE_FEATURE_X. Якщо ви #include "a.h"прокоментуєте, ваш файл може все-таки скомпілюватися, але може не зробити того, що ви очікуєте. Виявлення цього програмно нетривіально.

Незалежно від того, який інструмент робить це, потрібно знати і ваше середовище побудови. Якщо ах виглядає так:

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

Тоді USE_FEATURE_Xвизначається лише, якщо WINNTце визначено, тому інструмент повинен знати, які директиви генерує сам компілятор, а також які вказуються в команді компіляції, а не у файлі заголовка.


9

Як і Тіммерманс, я не знайомий з жодними інструментами для цього. Але я знаю програмістів, які написали скрипт Perl (або Python), щоб спробувати прокоментувати кожен рядок включення по одному, а потім скомпілювати кожен файл.


Схоже, що зараз у Еріка Реймонда для цього є інструмент .

Google cpplint.py має правило "включити те, що ви використовуєте" (серед багатьох інших), але, наскільки я можу зрозуміти, жодне "не включати лише те, що ви використовуєте". Незважаючи на це, це може бути корисним.


Мені довелося сміятися, коли я читав цю. Мій бос зробив саме це в одному з наших проектів минулого місяця. Зменшений заголовок включає пару факторів.
Дон Вейкфілд,

2
codewarrior на mac раніше вбудовував скрипт для цього, коментував, компілював, при помилці не коментував, продовжував до кінця #includes. Це працювало лише для #includes у верхній частині файлу, але зазвичай там вони знаходяться. Це не ідеально, але все ж тримає речі досить розумними.
slycrel

5

Якщо вас зацікавила ця тема загалом, ви можете перевірити великомасштабний дизайн програмного забезпечення C ++ від Lakos . Це трохи застаріле, але зачіпає багато питань "фізичного дизайну", таких як пошук абсолютного мінімуму заголовків, які потрібно включити. Я насправді ніде не бачив подібного роду обговорень.


4

Спробуйте програму Include Manager . Він легко інтегрується у Visual Studio та візуалізує ваші шляхи включення, що допомагає знаходити непотрібні речі. Внутрішньо він використовує Graphviz, але є набагато більше цікавих функцій. І хоча це комерційний продукт, він має дуже низьку ціну.



3

Якщо ваші заголовкові файли зазвичай починаються з

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

(на відміну від використання #pragma один раз) ви можете змінити це на:

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

Оскільки компілятор видає ім'я cpp-файлу, що компілюється, це дасть вам знати хоча б про те, який файл cpp викликає заголовок кілька разів.


12
Я думаю, що добре включати заголовки кілька разів. Добре включити те, що ви використовуєте, і не залежати від ваших файлів включення. Я думаю, що OP бажає знайти # включає, які насправді не використовуються.
Райан Гінстром,

12
ІМО активно робити неправильно. Заголовки повинні включати інші заголовки, коли вони не працювали б без них. І коли у вас є A.hі B.hщо обидва залежать, C.hі ви включаєте A.hі B.h, оскільки вам потрібні обидва, ви будете включати C.hдвічі, але це нормально, тому що компілятор пропустить його вдруге, і якщо ви цього не зробили, вам доведеться пам'ятати завжди включати C.hдо A.hабо B.hзакінчуючи набагато більш марними включеннями.
Ян Худец,

5
Вміст точний, це хороше рішення для пошуку заголовків, які включаються кілька разів. Однак на вихідне запитання це не дає відповіді, і я не уявляю, коли це було б гарною ідеєю. Файли Cpp повинні містити всі заголовки, від яких вони залежать, навіть якщо заголовок включений де-небудь ще. Ви не хочете, щоб ваш проект був складеним для замовлення, або ви вважаєте, що інший заголовок буде містити той, який вам потрібен.
jaypb 02

3

PC-Lint справді може це зробити. Один простий спосіб зробити це - налаштувати його на виявлення лише невикористаних файлів включення та ігнорування всіх інших проблем. Це досить просто - щоб увімкнути лише повідомлення 766 ("Файл заголовка, який не використовується в модулі"), просто включіть параметри -w0 + e766 у командний рядок.

Той самий підхід можна також використовувати із відповідними повідомленнями, такими як 964 ("Файл заголовка, який не використовується безпосередньо в модулі") та 966 ("Непрямо включений файл заголовка, який не використовується в модулі").

FWIW Я писав про це більш докладно в дописі в блозі минулого тижня за адресою http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318 .


2

Якщо ви хочете видалити непотрібні #includeфайли, щоб скоротити час збірки, ваш час і гроші, можливо, буде витрачено на розпаралелювання процесу збірки за допомогою cl.exe / MP , make -j , Xoreax IncrediBuild , distcc / icecream тощо.

Звичайно, якщо у вас вже є паралельний процес побудови, і ви все ще намагаєтеся його пришвидшити, то неодмінно очистіть свої #includeдирективи та видаліть непотрібні залежності.


2

Почніть з кожного файлу включення та переконайтеся, що кожен файл включення містить лише те, що необхідно для його компіляції. Будь-які файли включення, яких тоді немає для файлів C ++, можна додати до самих файлів C ++.

Для кожного файлу включення та джерела прокоментуйте кожен файл включення по одному та перевірте, чи він компілюється.

Також непогано сортувати файли включення за алфавітом, а якщо це неможливо, додайте коментар.


2
Я не впевнений, наскільки практичним є цей коментар, якщо задіяна дуже велика кількість файлів реалізації.
Sonny

1

Додавання одного або обох наведених нижче #defines виключить часто непотрібні файли заголовків і може істотно покращити час компіляції, особливо якщо код, який не використовує функції Windows API.

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

Див. Http://support.microsoft.com/kb/166474


1
Не потрібно обох - VC_EXTRALEAN визначає WIN32_LEAN_AND_MEAN
Ейдан Райан

1

Якщо ви цього ще не зробили, використання попередньо скомпільованого заголовка, щоб включити все те, що ви не збираєтеся змінювати (заголовки платформи, зовнішні заголовки SDK або статичні вже завершені частини вашого проекту), матиме величезну різницю в часі збірки.

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

Крім того, хоча для вашого проекту може бути занадто пізно, організація проекту за розділами та не об’єднання всіх локальних заголовків у один великий основний заголовок є гарною практикою, хоча для цього потрібно трохи додаткової роботи.


Прекрасне пояснення попередньо скомпільованих заголовків: cygnus-software.com/papers/precompiledheaders.html (Не впевнений, що автогенерація попередньо скомпільованих заголовків порушена в останніх версіях VisualStudio, але це варто перевірити.)
idbrii

1

Якщо ви працюєте з Eclipse CDT, ви можете спробувати http://includator.com для оптимізації вашої структури включення. Однак, Інклютор може не знати достатньо про заздалегідь визначені VC ++ включення та налаштування CDT для використання VC ++ з правильним включенням ще не вбудовано в CDT.


1

Остання версія середовища реалізації середовища Jetbrains, CLion, автоматично відображає (сірим кольором) елементи, які не використовуються у поточному файлі.

Також можна отримати список усіх невикористаних включень (а також функцій, методів тощо ...) з IDE.


0

Деякі з існуючих відповідей стверджують, що це важко. Це справді так, тому що вам потрібен повний компілятор, щоб виявити випадки, коли пряма декларація була б доречною. Ви не можете розібрати C ++, не знаючи, що означають символи; граматика просто занадто неоднозначна для цього. Ви повинні знати, чи певне ім’я називає клас (може бути оголошеним вперед) або змінною (не може). Крім того, ви повинні знати про простір імен.


Можна просто сказати: "Вирішення питання про те, який # включає, рівнозначно вирішенню проблеми зупинки. Удачі :)" Звичайно, ви можете використовувати евристику, але я не знаю жодного вільного програмного забезпечення, яке це робить.
porges

0

Можливо, трохи пізно, але одного разу я знайшов сценарій WebKit perl, який робив саме те, що ти хотів. Я потребую адаптації, я вважаю (я погано розбираюся в perl), але це має зробити трюк:

http://trac.webkit.org/browser/branches/old/safari-3-2-branch/WebKitTools/Scripts/find-extra-includes

(це стара гілка, оскільки в магістралі більше немає файлу)


0

Якщо є певний заголовок, який, на вашу думку, більше не потрібен (скажімо, string.h), ви можете прокоментувати, що включати, а потім поставити це нижче всіх включених:

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

Звичайно, ваші заголовки інтерфейсу можуть використовувати іншу конвенцію #define для запису їх включення до пам'яті CPP. Або жодної конвенції, і в цьому випадку такий підхід не спрацює.

Потім перебудуйте. Є три можливості:

  • Це будує нормально string.h не був критичним для компіляції, і включення для нього можна видалити.

  • Помилка # помилки. string.g було якось опосередковано включено. Ви все ще не знаєте, чи потрібно string.h. Якщо це потрібно, вам слід безпосередньо # включити (див. Нижче).

  • Ви отримуєте іншу помилку компіляції. string.h був потрібний і не включається опосередковано, тому включення було правильним для початку.

Зауважте, що залежно від непрямого включення, коли ваш .h або .c безпосередньо використовує інший .h - це майже напевно помилка: ви фактично обіцяєте, що ваш код вимагатиме цього заголовка лише до тих пір, поки це вимагатиме якийсь інший заголовок, який ви використовуєте, що, мабуть, не те, що ви мали на увазі.

Застереження, згадані в інших відповідях щодо заголовків, які змінюють поведінку, а не те, що декларування речей, які спричиняють помилки збірки, застосовуються і тут.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.