Очистка устаревшего кода «спагетти в заголовке»

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

  •  02-07-2019
  •  | 
  •  

Вопрос

Любые рекомендуемые практики для очистки «Spaghetti», которая вызывает чрезвычайно медленное время компиляции (Linux/Unix)?

Есть ли какой-нибудь эквивалент «#pragma Once» с GCC?
(обнаружены противоречивые сообщения по этому поводу)

Спасибо.

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

Решение

Предполагая, что вы знакомы с «защитой включения» (#ifdef в начале заголовка..), дополнительным способом ускорить время сборки является использование внешней защиты включения.Об этом говорилось в "Крупномасштабное проектирование программного обеспечения на C++".Идея состоит в том, что классические средства защиты включения, в отличие от #pragma Once, не избавляют вас от анализа препроцессора, необходимого для игнорирования заголовка со второго раза (т.е.ему все равно придется анализировать и искать начало и конец защиты включения.При использовании внешних средств защиты включения вы размещаете #ifdef вокруг самой строки #include.

Итак, это выглядит так:

#ifndef MY_HEADER
#include "myheader.h"
#endif

и, конечно же, в файле H у вас есть классическая защита включения

#ifndef MY_HEADER
#define MY_HEADER

// content of header

#endif

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

опять же, все это есть в этой книге.хз

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

Если вы хотите выполнить полную очистку и у вас есть на это время, то лучшим решением будет удалить все #include во всех файлах (кроме очевидных, например:abc.h в abc.cpp), а затем скомпилируйте проект.Добавьте необходимое предварительное объявление или заголовок, чтобы исправить первую ошибку, а затем повторяйте, пока не завершите работу правильно.

Это не устраняет основные проблемы, которые могут привести к проблемам с включением, но гарантирует, что единственные включения являются необходимыми.

Я читал, что GCC считает #pragma once устарел, хотя даже #pragma once может сделать очень многое, чтобы ускорить процесс.

Чтобы попытаться распутать #include спагетти, можешь посмотреть доксиген.Он должен иметь возможность генерировать графики включенных заголовков, что может дать вам преимущество в упрощении.Я не могу вспомнить подробности навскидку, но функции графа могут потребовать установки ГрафВиз и укажите doxygen путь, по которому он может найти dotty.exe GraphViz.

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

На днях я прочитал об изящном трюке, позволяющем уменьшить зависимости заголовков:Напишите сценарий, который будет

  • найти все операторы #include
  • удалять по одному оператору за раз и перекомпилировать
  • если компиляция не удалась, добавьте оператор включения обратно в

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

Еще несколько примечаний:

  • Современные компиляторы (среди них gcc) распознают защиту заголовков и оптимизируют так же, как когда-то это делала pragma, открывая файл только один раз.
  • pragma Once может быть проблематичным, если один и тот же файл имеет разные имена в файловой системе (т.с мягкими ссылками)

  • gcc поддерживает #pragma один раз, но называет его «устаревшим».
  • pragma Once поддерживается не всеми компиляторами и не является частью стандарта C.

  • не только компиляторы могут быть проблематичными.Такие инструменты, как Incredibuild, также имеют проблемы с #pragma Once.

Ричард был в чем-то прав (Почему его решение было записано?).

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

Здесь говорилось либо:

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

2. Ваш устаревший код все еще жив.Затем вы либо используете предварительно скомпилированные заголовки и/или средства защиты/внешние средства защиты в качестве временного решения, но в конечном итоге вам нужно будет удалить все ваши включения, по одному .C или .CPP за раз, и скомпилировать каждый . C или .CPP по одному, исправляя их включения с помощью опережающих объявлений или включений, когда это необходимо (или даже разбивая большое включение на более мелкие, чтобы быть уверенным, что каждый файл .C или .CPP получит только те заголовки, которые ему нужны).В любом случае, тестирование и удаление устаревших включений — это часть сопровождения проекта, так что...

Мой собственный опыт работы с предварительно скомпилированными заголовками был не совсем удачным, потому что в половине случаев компилятор не мог найти определенный мной символ, и поэтому я попробовал полную «очистку/перестройку», чтобы убедиться, что это не предварительно скомпилированный заголовок. это устарело.Поэтому я предполагаю использовать его для внешних библиотек, к которым вы даже не будете прикасаться (например, заголовки STL, C API, Boost и т. д.).Тем не менее, мой собственный опыт был связан с Visual C++ 6, так что я думаю (надеюсь?), что теперь они все поняли правильно.

Теперь последнее:Заголовки всегда должны быть самодостаточными.Это означает, что если включение заголовков зависит от порядка включения, то у вас есть проблема.Например, если вы можете написать:

#include "AAA.hpp"
#include "BBB.hpp"

Но нет:

#include "BBB.hpp"
#include "AAA.hpp"

поскольку BBB зависит от AAA, то все, что у вас есть, — это зависимость, которую вы никогда не признавали в коде.Если вы не подтвердите это с помощью определения, ваша компиляция превратится в кошмар.BBB также должен включать AAA (даже если это может быть несколько медленнее:в конце концов, предварительные объявления в любом случае будут очищать ненужные включения, поэтому у вас должен быть более быстрый таймер компиляции).

Используйте один или несколько из них, чтобы ускорить время сборки.

  1. Используйте предварительно скомпилированные заголовки
  2. Используйте механизм кэширования (например, scons)
  3. Используйте распределенную систему сборки ( distcc, Incredibuild($) ).

В заголовках:включайте заголовки только в том случае, если вы не можете использовать предварительное объявление, но всегда #include любой файл, который вам нужен (включать зависимости — это зло!).

Как упоминалось в другом ответе, вам обязательно следует использовать предварительные объявления, когда это возможно.Насколько мне известно, в GCC нет ничего эквивалентного #pragma Once, поэтому я придерживаюсь старомодного стиля включения защиты.

Спасибо за ответы, но вопрос касается существующего кода, который включает строгий «порядок включения» и т. д.Вопрос в том, есть ли какие-нибудь инструменты/скрипты, позволяющие прояснить, что на самом деле происходит.

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

ПК-Линт будет иметь большое значение для очистки головок спагетти.Также это решит и другие проблемы для вас, например, невидимые неинициализированные переменные и т. д.

Как прокомментировал onebyone.livejournal.com в ответ на ваш вопрос, некоторые компиляторы поддерживают включить оптимизацию защиты, который страница, на которую я ссылаюсь, определяет следующим образом:

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

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

  • Каждый .c или .cpp файл должен #include соответствующий .h сначала файл, а остальная его часть #include директивы должны быть отсортированы в алфавитном порядке.Обычно вы получаете ошибки сборки, когда это нарушает неустановленные зависимости между файлами заголовков.
  • Если у вас есть файл заголовка, который определяет глобальные определения типов для базовых типов или глобальных #define директивы, которые используются для большей части кода, каждая .h файл должен #include сначала этот файл, а остальная часть его #include директивы должны быть отсортированы в алфавитном порядке.
  • Когда эти изменения вызывают ошибки компиляции, вам обычно придется добавить явную зависимость от одного заголовочного файла к другому в форме #include.
  • Если эти изменения не вызывают ошибок компиляции, они могут вызвать изменения в поведении.Надеюсь, у вас есть какой-то набор тестов, который вы можете использовать для проверки функциональности вашего приложения.

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

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