it-roy-ru.com

Как мне обнаружить ненужные файлы #include в большом проекте C ++?

Я работаю над большим проектом C++ в Visual Studio 2008, и есть много файлов с ненужными директивами #include. Иногда #includes - это просто артефакты, и все будет хорошо скомпилировано с их удалением, а в других случаях классы могут быть заранее объявлены, а #include может быть перемещено в файл .cpp. Есть ли хорошие инструменты для обнаружения обоих этих случаев?

89
shambolic

Хотя в Visual Studio не отображаются ненужные включаемые файлы, в Visual Studio есть параметр /showIncludes (щелчок правой кнопкой мыши на файле .cpp, Properties->C/C++->Advanced), который выводит дерево всех включенных файлов во время компиляции. Это может помочь в определении файлов, которые не должны быть включены.

Вы также можете взглянуть на идиому pimpl, чтобы у вас было меньше зависимостей заголовочных файлов, чтобы было легче увидеть, что вы можете удалить.

45
Eclipse

PC Lint хорошо работает для этого и находит для вас и всякие другие глупые проблемы. У него есть параметры командной строки, которые можно использовать для создания внешних инструментов в Visual Studio, но я обнаружил, что с надстройкой Visual Lint легче работать. Помогает даже бесплатная версия Visual Lint. Но попробуйте PC-Lint. Настройка его так, чтобы он не давал вам слишком много предупреждений, занимает немного времени, но вы будете удивлены тем, что он получится.

28
Joe

Существует новый инструмент на основе Clang, include-what-you-use , который предназначен для этого.

26
Josh Kelley

!! ОТКАЗ !! Я работаю над коммерческим инструментом статического анализа (не PC Lint). !! ОТКАЗ !!

Есть несколько проблем с простым подходом без разбора:

1) Наборы перегрузки:

Возможно, перегруженная функция имеет объявления из разных файлов. Возможно, удаление одного заголовочного файла приведет к выбору другой перегрузки, а не к ошибке компиляции! Результатом будет тихая смена семантики, которую потом будет очень трудно отследить.

2) Шаблон специализации:

Как и в примере с перегрузкой, если у вас есть частичная или явная специализация для шаблона, вы хотите, чтобы все они были видны при использовании шаблона. Возможно, специализации для основного шаблона находятся в разных заголовочных файлах. Удаление заголовка со специализацией не приведет к ошибке компиляции, но может привести к неопределенному поведению, если бы эта специализация была выбрана. (См .: Видимость шаблонной специализации функции C++ )

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

25
Richard Corden

Я не знаю ни одного такого инструмента, и я думал о том, чтобы написать его в прошлом, но оказывается, что эту проблему трудно решить.

Скажем, ваш исходный файл включает в себя a.h и b.h; a.h содержит #define USE_FEATURE_X, а b.h использует #ifdef USE_FEATURE_X. Если #include "a.h" закомментирован, ваш файл может по-прежнему компилироваться, но может не выполнять то, что вы ожидаете. Обнаружение этого программно нетривиально.

Какой бы инструмент это ни делал, он должен знать и вашу среду сборки. Если a.h выглядит так:

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

Тогда USE_FEATURE_X определяется, только если определено WINNT, поэтому инструмент должен знать, какие директивы генерируются самим компилятором, а также какие указываются в команде compile, а не в заголовочном файле.

10
Graeme Perrow

Как и Timmermans, я не знаком с какими-либо инструментами для этого. Но у меня есть известные программисты, которые написали скрипт на Perl (или Python), чтобы попытаться закомментировать каждую строку включения по одной, а затем скомпилировать каждый файл.


Похоже, что теперь Эрик Рэймонд есть инструмент для этого .

В Google cpplint.py есть правило "включать то, что вы используете" (среди многих других), но, насколько я могу судить, нет "только включать что вы используете. " Тем не менее, это может быть полезно.

9
Max Lybbert

Если вам вообще интересна эта тема, вы можете проверить Lakos ' Large Scale C++ Software Design . Он немного устарел, но затрагивает множество проблем "физического проектирования", таких как поиск абсолютного минимума заголовков, которые необходимо включить. Я действительно никогда не видел, чтобы подобные вещи обсуждались где-либо еще.

5
Adrian

Дайте Включить менеджер попробовать. Он легко интегрируется в Visual Studio и визуализирует пути включения, которые помогают вам находить ненужные вещи. Внутренне он использует Graphviz, но есть еще много интересных функций. И хотя это коммерческий продукт, он имеет очень низкую цену.

4
Alex

Вы можете построить граф включения, используя C/C++ Watcher Dependencies Include File , и найти ненужные включения визуально.

4
Vladimir

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

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

FWIW Я написал об этом более подробно в блоге на прошлой неделе на http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318 .

3
Anna-Jayne Metcalfe

Если ваши заголовочные файлы обычно начинаются с

#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 вызывает многократный перенос заголовка.

3
Sam

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

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

2
bk1e

Начните с каждого включаемого файла и убедитесь, что каждый включаемый файл содержит только то, что необходимо для его компиляции. Любые включаемые файлы, которые затем отсутствуют для файлов C++, могут быть добавлены в сами файлы C++.

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

Также рекомендуется сортировать включаемые файлы в алфавитном порядке, а там, где это невозможно, добавьте комментарий.

2
selwyn

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

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

Смотрите http://support.Microsoft.com/kb/166474

1
Roger Nelson

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

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

Кроме того, хотя это может быть слишком поздно для вашего проекта, хорошей практикой является организация вашего проекта по разделам, а не объединение всех локальных заголовков в один большой главный заголовок, хотя это требует немного дополнительной работы.

1
anon6439

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

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

1
Jean-Michaël Celerier

Если вы будете работать с Eclipse CDT, вы можете попробовать http://includator.com оптимизировать структуру включения. Однако Includator может не знать достаточно о предопределенных включениях VC++, а настройка CDT для использования VC++ с правильными включениями еще не встроена в CDT.

1
PeterSom

Может быть, немного поздно, но однажды я нашел Perl-скрипт WebKit, который делал то, что вы хотели. Я думаю, что потребуется некоторая адаптация (я не очень хорошо разбираюсь в Perl), но это должно сработать:

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

(это старая ветка, потому что в стволе больше нет файла)

0
rubenvb

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

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

Конечно, ваши заголовки интерфейса могут использовать другое соглашение #define для записи их включения в память CPP. Или нет соглашения, в этом случае этот подход не будет работать.

Затем восстановите. Есть три варианта:

  • Строит нормально. string.h не был критичным для компиляции, и include для него можно удалить.

  • # Ошибка отключается. string.g был каким-то образом включен косвенно. Вы все еще не знаете, требуется ли string.h. Если это необходимо, вы должны напрямую включить его (см. Ниже).

  • Вы получаете другую ошибку компиляции. string.h был необходим и не был включен косвенно, поэтому включение было правильным с самого начала.

Обратите внимание, что в зависимости от косвенного включения, когда ваш .h или .c напрямую использует другой .h, это почти наверняка ошибка: вы фактически обещаете, что ваш код будет запрашивать этот заголовок только тогда, когда этого требует другой используемый вами заголовок, что, вероятно, не то, что вы имели в виду.

Предостережения, упомянутые в других ответах о заголовках, которые изменяют поведение, а не объявляют вещи, которые вызывают сбои сборки, применимы и здесь.

0
Britton Kerin

Некоторые из существующих ответов утверждают, что это сложно. Это действительно так, потому что вам нужен полный компилятор для обнаружения случаев, когда было бы уместным предварительное объявление. Вы не можете разобрать C++, не зная, что означают символы; грамматика просто слишком неоднозначна для этого. Вы должны знать, называет ли определенное имя класс (может быть объявлен заранее) или переменную (не может). Кроме того, вы должны быть осведомлены о пространстве имен.

0
MSalters