Можно ли использовать препроцессор C, чтобы определить, существует ли файл? - PullRequest
55 голосов
/ 27 сентября 2008

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

Есть несколько мест в кодовой базе, где он мог бы существенно очистить код, если бы только был способ заставить препроцессор игнорировать определенные #includes, если файл не существовал в текущей папке. Кто-нибудь знает способ достичь этого?

В настоящее время мы используем #ifdef вокруг #include в общем файле со вторым файлом, специфичным для проекта, который # определяет, существует или нет #include в проекте. Это работает, но это некрасиво. Люди часто забывают правильно обновить определения, когда они добавляют или удаляют файлы из проекта. Я планировал написать инструмент предварительной сборки, чтобы поддерживать этот файл в актуальном состоянии, но если есть независимый от платформы способ сделать это с препроцессором, я бы предпочел сделать это вместо этого. Есть идеи?

Ответы [ 9 ]

52 голосов
/ 21 октября 2015

Маленькое обновление

Некоторые компиляторы могут поддерживать __has_include ( header-name ).

Расширение добавлено в стандарт C ++ 17 ( P0061R1 ).

Поддержка компилятора

  • Clang
  • GCC от 5.X
  • Visual Studio от VS2015, обновление 2 (?)

Пример (с сайта clang):

// Note the two possible file name string formats.
#if __has_include("myinclude.h") && __has_include(<stdint.h>)
# include "myinclude.h"
#endif

Источники

43 голосов
/ 27 сентября 2008

Создайте специальную папку для отсутствующих заголовков и сделайте эту папку для поиска последней
(это зависит от компилятора - последний элемент в переменной среды "INCLUDES", что-то вроде этого)

Тогда, если какой-то header1.h может отсутствовать, создайте в этой папке заглушку

header1.h:

#define header1_is_missing

Теперь вы всегда можете написать

#include <header1.h>
#ifdef header1_is_missing

   // there is no header1.h 

#endif
30 голосов
/ 27 сентября 2008

Обычно это делается с помощью скрипта, который пытается запустить препроцессор при попытке включения файла. В зависимости от того, возвращает ли препроцессор ошибку, сценарий обновляет сгенерированный файл .h с помощью соответствующего #define (или #undef). В bash скрипт может выглядеть примерно так:

cat > .test.h <<'EOM'
#include <asdf.h>
EOM
if gcc -E .test.h
 then
  echo '#define HAVE_ASDF_H 1' >> config.h
 else 
  echo '#ifdef HAVE_ASDF_H' >> config.h
  echo '# undef HAVE_ASDF_H' >> config.h
  echo '#endif' >> config.h
 fi

Довольно тщательный фреймворк для переносимой работы с такими проверками переносимости (как и тысячи других): autoconf .

7 голосов
/ 27 сентября 2008

Сам препроцессор не может идентифицировать существование файлов, но вы, безусловно, можете использовать для этого среду сборки. Я в основном знаком с make, который позволит вам сделать что-то подобное в вашем make-файле:

ifdef $(test -f filename && echo "present")
  DEFINE=-DFILENAME_PRESENT
endif

Конечно, вам придется найти аналог этого в других средах сборки, таких как VisualStudio, но я уверен, что они существуют.

4 голосов
/ 27 сентября 2008

Другая возможность: заполнить каталог где-нибудь версиями нулевой длины всех заголовков, которые вы хотите включить. Передайте аргумент -I в этот каталог как последний такой параметр.

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

Я предполагаю, что другие реализации cpp также ищут свои каталоги включения в указанном порядке.

4 голосов
/ 27 сентября 2008

Насколько я знаю, cpp не имеет директивы относительно существования файла.

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

foo.o: foo.c
    if [ -f header1.h ]; then CFLAGS+=-DHEADER1_INC

Как упоминает @Greg Hewgill, вы можете сделать ваши #include условными:

#ifdef HEADER1_INC
#include <header1.h>
#endif
4 голосов
/ 27 сентября 2008

Вы можете выполнить шаг предварительной сборки, который сгенерирует включаемый файл, который содержит список #defines, представляющих имена файлов, существующих в текущем каталоге:

#define EXISTS_FILE1_C
#define EXISTS_FILE1_H
#define EXISTS_FILE2_C

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

2 голосов
/ 02 июля 2009

Мне пришлось сделать что-то подобное для Symbian OS. Вот как я это сделал: Допустим, вы хотите проверить, существует ли файл «file_strange.h», и хотите включить некоторые заголовки или ссылку на некоторые библиотеки в зависимости от наличия этого файла.

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

autoconf хорош, но слишком много для многих небольших проектов.

---------- check.bat

@echo off

IF EXIST [\epoc32\include\domain\middleware\file_strange] GOTO NEW_API
GOTO OLD_API
GOTO :EOF

:NEW_API
echo.#define NEW_API_SUPPORTED>../inc/file_strange_supported.h
GOTO :EOF

:OLD_API
echo.#define OLD_API_SUPPORTED>../inc/file_strange_supported.h
GOTO :EOF

---------- check.bat заканчивается

затем я создал файл gnumake

---------- checkmedialist.mk

do_nothing :
    @rem do_nothing

MAKMAKE : 
        check.bat

BLD : do_nothing

CLEAN : do_nothing

LIB : do_nothing

CLEANLIB : do_nothing

RESOURCE : do_nothing

FREEZE : do_nothing

SAVESPACE : do_nothing

RELEASABLES : do_nothing

FINAL : do_nothing

---------- check.mk заканчивается

включите файл check.mk в ваш файл bld.inf, он ДОЛЖЕН быть раньше ваших файлов MMP

PRJ_MMPFILES
gnumakefile checkmedialist.mk

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

#include "../inc/file_strange_supported.h"
#ifdef NEW_API_SUPPORTED
LIBRARY newapi.lib
#else
LIBRARY oldapi.lib
#endif

и в .cpp

#include "../inc/file_strange_supported.h"
#ifdef NEW_API_SUPPORTED
CStrangeApi* api = Api::NewLC();
#else
// ..
#endif
1 голос
/ 11 сентября 2018

Вопреки некоторым утверждениям здесь и в Интернете, Visual Studio 2015 НЕ поддерживает функцию __has_include - по крайней мере, по моему опыту. Протестировано с обновлением 3.

Слухи могли возникнуть из-за того факта, что VS 2017 также упоминается как «Версия 15»; VS 2015 вместо этого упоминается как «Версия 14». Поддержка этой функции, по-видимому, официально представлена ​​в Visual Studio 2017 версии 15.3.

...