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

StackOverflow https://stackoverflow.com/questions/74326

Вопрос

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

Это было полезно?

Решение

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

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

Другие советы

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

Появился новый инструмент на основе Clang, включить то, что вы используете, который стремится сделать это.

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

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

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

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

2) Специализации шаблона:

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

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

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

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

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

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

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

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


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

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

Если вас интересует эта тема в целом, возможно, вам стоит заглянуть к Лакосу. Крупномасштабное проектирование программного обеспечения на C++.Он немного устарел, но затрагивает множество вопросов «физического дизайна», таких как поиск абсолютного минимума заголовков, которые необходимо включить.Я действительно не видел, чтобы подобные вещи обсуждались где-либо еще.

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

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

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

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

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

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

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

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

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

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

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

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

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

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

Видеть http://support.microsoft.com/kb/166474

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

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

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

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

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

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

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

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

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

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

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

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

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

Потом восстановить.Есть три возможности:

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top