Подтвердить что ты не робот

Как запустить препроцессор только для локальных заголовков?

Я хочу, чтобы препроцессор читал в составе локальных заголовков, но игнорировал включения заголовков системы. Иначе говоря, как я могу заставить препроцессор пропустить препроцессорные директивы формы:

#include <h-char-sequence> new-line

но все же обрабатывают директивы формы:

#include "q-char-sequence" new-line

В качестве примера кода обратите внимание на следующий файл:

#include <iostream>     //system
#include "class_a.hpp"  //local
#include <string>       //system
#include "class_b.hpp"  //local

int main() {}

как я могу получить вывод препроцессора:

#include <iostream>
class A{};
#include <string>
class B{};

int main() {}

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


В gcc мой вызов выглядит так: g++ -E -P main.cpp, где -E останавливается после предварительной обработки, а -P исключает генерацию маркеров строки.
Я не могу найти флаг, исключающий обработку заголовков системы.

4b9b3361

Ответ 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 является то, что маловероятно, что у вас есть что-то, использующее это как имя макроса.

Автоматическое создание суррогатных заголовков

osgx спрашивает:

Как мы можем автоматизировать генерацию топовых системных заголовков?

Существует множество вариантов. Один из них - проанализировать ваш код (например, с помощью 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 будет принимать аргумент, определяющий суррогатный каталог заголовков.

Ответ 2

С помощью clang вы можете сделать, например:

 clang -Imyinclude -P -E -nostdinc -nobuiltininc main.cpp

Кажется, что нет способа сохранить системные строки #include, которые он не может найти.

Это не работает для gcc, поскольку его препроцессор остановится при использовании -nostdinc и не сможет найти заголовочный файл #included.

Ответ 3

Вы можете поместить #define SYSTEM_HEADERS 0 в заголовок конфигурации и сделать это следующим образом

#include "config.h" // the configuration header
#include "class_a.hpp"
#include "class_b.hpp"

#if SYSTEM_HEADERS // which is #if 0
#include <iostream>
#include <string>
#endif

и если вы хотите получить заголовки системы, вы можете сделать его #define SYSTEM_HEADERS 1, который будет включать в себя заголовки системы.