Ответ 1
Сколько усилий вы готовы пойти? Там неприятный неясный способ сделать это, но он требует, чтобы вы создали фиктивный каталог для хранения суррогатов для заголовков системы. OTOH, он не требует каких-либо изменений в каком-либо из ваших исходных кодов. Эта же методика одинаково хорошо работает для кода C.
Настройка
Файлы:
./class_a.hpp
./class_b.hpp
./example.cpp
./system-headers/iostream
./system-headers/string
"Заголовки системы", такие как ./system-headers/iostream
, содержат одну строку (в этой строке нет #
!):
include <iostream>
Каждый класс заголовков содержит одну строку:
class A{};
Содержимое example.cpp
- это то, что вы показываете в вопросе:
#include <iostream> //system
#include "class_a.hpp" //local
#include <string> //system
#include "class_b.hpp" //local
int main() {}
Запуск препроцессора C
Запуск препроцессора C, подобного этому, приводит к показанному результату:
$ cpp -Dinclude=#include -I. -Isystem-headers example.cpp
# 1 "example.cpp"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "example.cpp"
# 1 "system-headers/iostream" 1
#include <iostream>
# 2 "example.cpp" 2
# 1 "class_a.hpp" 1
class A{};
# 3 "example.cpp" 2
# 1 "system-headers/string" 1
#include <string>
# 4 "example.cpp" 2
# 1 "class_b.hpp" 1
class B{};
# 5 "example.cpp" 2
int main() {}
$
Если вы удалите строки # n
, это будет:
$ cpp -Dinclude=#include -I. -Isystem-headers example.cpp | grep -v '^# [0-9]'
#include <iostream>
class A{};
#include <string>
class B{};
int main() {}
$
которые дают или принимают пробел в начале строк, содержащих #include
, это то, что вы хотели.
Анализ
Аргумент -Dinclude=#include
эквивалентен #define include #include
. Когда препроцессор генерирует вывод из макроса, даже если он похож на директиву (например, #include
), это не препроцессорная директива. Цитируя стандарт С++ 11 ISO/IEC 14882: 2011 (не то, что это изменилось между версиями AFAIK - и, дословно, то, что он говорит в стандарте C11, ISO/IEC 9899: 2011 тоже, в §6.10.3)
§16.3 Замена макроса
¶8 Если токен
#
предварительной обработки, за которым следует идентификатор, лексически происходит в точке, в которой может начинаться директива предварительной обработки, идентификатор не подлежит замене макроса.§16.3.4 Повторное сканирование и дальнейшая замена
¶2 Если имя заменяемого макроса будет обнаружено во время этого сканирования списка замены (не считая остальных токенов предварительной обработки исходных файлов), он не будет заменен....
¶3 Полученная полностью макрозаменяемая последовательность токенов предварительной обработки не обрабатывается как директива предварительной обработки, даже если она похожа на одну,...
Когда препроцессор встречает #include <iostream>
, он просматривает текущую директорию и не находит файл, затем смотрит в ./system-headers
и находит файл iostream
, поэтому он обрабатывает это на выходе. Он содержит одну строку, include <iostream>
. Поскольку include
является макросом, он расширяется (до #include
), но дальнейшее расширение предотвращается, а #
не обрабатывается как директива из-за §16.3.4 ¶3. Таким образом, вывод содержит #include <iostream>
.
Когда препроцессор встречает #include "class_a.hpp"
, он просматривает текущий каталог и находит файл и включает его содержимое в вывод.
Промыть и повторить для других заголовков. Если class_a.hpp
содержит #include <iostream>
, то это снова расширяется до #include <iostream>
(с ведущим пространством). Если в вашем каталоге system-headers
отсутствует какой-либо заголовок, препроцессор будет искать в обычных местах и найти и включить его. Если вы используете только компилятор, а не cpp
, вы можете запретить ему просматривать в системных каталогах с помощью -nostdinc
- поэтому препроцессор будет генерировать ошибку, если system-headers
отсутствует заголовок (суррогат для a).
$ g++ -E -nostdinc -Dinclude=#include -I. -Isystem-headers example.cpp | grep -v '^# [0-9]'
#include <iostream>
class A{};
#include <string>
class B{};
int main() {}
$
Обратите внимание, что очень легко создавать заголовки суррогатной системы:
for header in algorithm chrono iostream string …
do echo "include <$header>" > system-headers/$header
done
JFTR, тестирование было выполнено на Mac OS X 10.11.5 с GCC 6.1.0. Если вы используете GCC (сборник компиляторов GNU, с примерами компиляторов примера gcc
и g++
), ваш пробег не должен сильно отличаться с какой-либо правдоподобной альтернативной версией.
Если вам неудобно использовать имя макроса include
, вы можете изменить его на все, что вам подходит - syzygy
, apoplexy
, nadir
, reinclude
,... - и изменить суррогатные заголовки использовать это имя и определить это имя в командной строке препроцессора (компилятора). Одним из преимуществ include
является то, что маловероятно, что у вас есть что-то, использующее это как имя макроса.
Автоматическое создание суррогатных заголовков
Как мы можем автоматизировать генерацию топовых системных заголовков?
Существует множество вариантов. Один из них - проанализировать ваш код (например, с помощью grep
), чтобы найти имена, которые могут быть или могут быть указаны, и создать соответствующие суррогатные заголовки. Неважно, если вы создадите несколько неиспользуемых заголовков - они не повлияют на процесс. Обратите внимание: если вы используете #include <sys/wait.h>
, суррогат должен быть ./system-headers/sys/wait.h
; что немного усложняет показанный код оболочки, но не очень. Другой способ - посмотреть заголовки в системных заголовочных каталогах (/usr/include
, /usr/local/include
и т.д.) И генерировать суррогаты для найденных там заголовков.
Например, mksurrogates.sh
может быть:
#!/bin/sh
sysdir="./system-headers"
for header in "[email protected]"
do
mkdir -p "$sysdir/$(dirname $header)"
echo "include <$header>" > "$sysdir/$header"
done
И мы можем написать listsyshdrs.sh
, чтобы найти заголовки системы, указанные в исходном коде в именованном каталоге:
#!/bin/sh
grep -h -e '^[[:space:]]*#[[:space:]]*include[[:space:]]*<[^>]*>' -r "${@:-.}" |
sed 's/^[[:space:]]*#[[:space:]]*include[[:space:]]*<\([^>]*\)>.*/\1/' |
sort -u
С добавлением немного форматирования, который сгенерировал список заголовков, подобных этому, когда я просмотрел исходное дерево с моими ответами на вопросы SO:
algorithm arpa/inet.h assert.h cassert
chrono cmath cstddef cstdint
cstdlib cstring ctime ctype.h
dirent.h errno.h fcntl.h float.h
getopt.h inttypes.h iomanip iostream
limits.h locale.h map math.h
memory.h netdb.h netinet/in.h pthread.h
semaphore.h signal.h sstream stdarg.h
stdbool.h stddef.h stdint.h stdio.h
stdlib.h string string.h sys/ipc.h
sys/mman.h sys/param.h sys/ptrace.h sys/select.h
sys/sem.h sys/shm.h sys/socket.h sys/stat.h
sys/time.h sys/timeb.h sys/times.h sys/types.h
sys/wait.h termios.h time.h unistd.h
utility vector wchar.h
Итак, чтобы сгенерировать суррогаты для исходного дерева в текущем каталоге:
$ sh mksurrogatehdr.sh $(sh listsyshdrs.sh)
$ ls -lR system-headers
total 344
-rw-r--r-- 1 jleffler staff 20 Jul 2 17:27 algorithm
drwxr-xr-x 3 jleffler staff 102 Jul 2 17:27 arpa
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 assert.h
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 cassert
-rw-r--r-- 1 jleffler staff 17 Jul 2 17:27 chrono
-rw-r--r-- 1 jleffler staff 16 Jul 2 17:27 cmath
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 cstddef
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 cstdint
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 cstdlib
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 cstring
-rw-r--r-- 1 jleffler staff 16 Jul 2 17:27 ctime
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 ctype.h
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 dirent.h
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 errno.h
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 fcntl.h
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 float.h
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 getopt.h
-rw-r--r-- 1 jleffler staff 21 Jul 2 17:27 inttypes.h
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 iomanip
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 iostream
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 limits.h
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 locale.h
-rw-r--r-- 1 jleffler staff 14 Jul 2 17:27 map
-rw-r--r-- 1 jleffler staff 17 Jul 2 17:27 math.h
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 memory.h
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 netdb.h
drwxr-xr-x 3 jleffler staff 102 Jul 2 17:27 netinet
-rw-r--r-- 1 jleffler staff 20 Jul 2 17:27 pthread.h
-rw-r--r-- 1 jleffler staff 22 Jul 2 17:27 semaphore.h
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 signal.h
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 sstream
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 stdarg.h
-rw-r--r-- 1 jleffler staff 20 Jul 2 17:27 stdbool.h
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 stddef.h
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 stdint.h
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 stdio.h
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 stdlib.h
-rw-r--r-- 1 jleffler staff 17 Jul 2 17:27 string
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 string.h
drwxr-xr-x 16 jleffler staff 544 Jul 2 17:27 sys
-rw-r--r-- 1 jleffler staff 20 Jul 2 17:27 termios.h
-rw-r--r-- 1 jleffler staff 17 Jul 2 17:27 time.h
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 unistd.h
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 utility
-rw-r--r-- 1 jleffler staff 17 Jul 2 17:27 vector
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 wchar.h
system-headers/arpa:
total 8
-rw-r--r-- 1 jleffler staff 22 Jul 2 17:27 inet.h
system-headers/netinet:
total 8
-rw-r--r-- 1 jleffler staff 23 Jul 2 17:27 in.h
system-headers/sys:
total 112
-rw-r--r-- 1 jleffler staff 20 Jul 2 17:27 ipc.h
-rw-r--r-- 1 jleffler staff 21 Jul 2 17:27 mman.h
-rw-r--r-- 1 jleffler staff 22 Jul 2 17:27 param.h
-rw-r--r-- 1 jleffler staff 23 Jul 2 17:27 ptrace.h
-rw-r--r-- 1 jleffler staff 23 Jul 2 17:27 select.h
-rw-r--r-- 1 jleffler staff 20 Jul 2 17:27 sem.h
-rw-r--r-- 1 jleffler staff 20 Jul 2 17:27 shm.h
-rw-r--r-- 1 jleffler staff 23 Jul 2 17:27 socket.h
-rw-r--r-- 1 jleffler staff 21 Jul 2 17:27 stat.h
-rw-r--r-- 1 jleffler staff 21 Jul 2 17:27 time.h
-rw-r--r-- 1 jleffler staff 22 Jul 2 17:27 timeb.h
-rw-r--r-- 1 jleffler staff 22 Jul 2 17:27 times.h
-rw-r--r-- 1 jleffler staff 22 Jul 2 17:27 types.h
-rw-r--r-- 1 jleffler staff 21 Jul 2 17:27 wait.h
$
Это предполагает, что имена заголовочных файлов не содержат пробелов, что не является необоснованным - это был бы храбрый программист, который создал имена заголовочных файлов с пробелами или другими сложными символами.
Полная готовая версия mksurrogates.sh
будет принимать аргумент, определяющий суррогатный каталог заголовков.