C 预处理器可以用来判断文件是否存在吗?

我有一个非常大的代码库(可以读取: 成千上万个模块) ,这些代码在许多项目中共享,这些项目都运行在不同的操作系统和不同的 C + + 编译器上。不用说,维护构建过程可能非常麻烦。

在代码库中有几个地方,如果当前文件夹中不存在某个 #includes文件,那么只要有一种方法可以让预处理器忽略这个 #includes文件,就可以大大清理代码。有人知道怎么做吗?

目前,我们在共享文件中使用 #include周围的 #ifdef,并使用第二个特定于项目的文件 # 定义项目中是否存在 #include。这个可以,但是很难看。在从项目中添加或删除文件时,人们常常忘记正确更新定义。我曾经考虑过编写一个预构建工具来使这个文件保持最新,但是如果有一个平台独立的方法来使用预处理器来实现这一点,我更愿意这样做。有什么想法吗?

53763 次浏览

You could have a pre-build step run that generates an include file that contains a list of #defines that represent the names of the files existing in the current directory:

#define EXISTS_FILE1_C
#define EXISTS_FILE1_H
#define EXISTS_FILE2_C

Then, include that file from within your source code, and then your source can test the EXISTS_* defines to see whether a file exists or not.

Generally this is done by using a script that tries running the preprocessor on an attempt at including the file. Depending on if the preprocessor returns an error, the script updates a generated .h file with an appropriate #define (or #undef). In bash, the script might look vaguely like this:

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

A pretty thorough framework for portably working with portability checks like this (as well as thousands others) is autoconf.

The preprocessor itself cannot identify the existence of files but you certainly can use the build environment to do so. I'm mostly familiar with make, which would allow you to do something like this in your makefile:

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

Of course, you'd have to find an analog to this in other build environments like VisualStudio, but I'm sure they exist.

So far as I know cpp does not have a directive regarding the existence of a file.

You might be able to accomplish this with a bit of help from the Makefile, if you're using the same make across platforms. You can detect the presence of a file in the Makefile:

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

As @Greg Hewgill mentions, you can then make your #includes be conditional:

#ifdef HEADER1_INC
#include <header1.h>
#endif

Another possibility: populate a directory somewhere with zero-length versions of all of the headers you wish to optionally include. Pass a -I argument to this directory as the last such option.

The GCC cpp searches its include directories in order, if it finds a header file in an earlier directory it will use it. Otherwise, it will eventually find the zero-length file, and be happy.

I presume that other cpp implementations also search their include directories in the order specified.

Create a special folder for missing headers, and make that folder to be searched last
(that is compliler specific - last item in "INCLUDES" environment variable, something like that)

Then if some header1.h can be missing, create in that folder a stub

header1.h:

#define header1_is_missing

Now you can always write

#include <header1.h>
#ifdef header1_is_missing


// there is no header1.h


#endif

I had to do something similar for the Symbian OS. This is how i did it: lets say you want to check if the file "file_strange.h" exists and you want to include some headers or link to some libraries depending on the existance of that file.

first creat a small batch file for checking the existence of that file.

autoconf is good but an over kill for many small projects.

----------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 ends

then i created a gnumake file

----------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 ends

include the check.mk file in your bld.inf file, it MUST be before your MMP files

PRJ_MMPFILES
gnumakefile checkmedialist.mk

now at compile time the file file_strange_supported.h will have an appropriate flag set. you can use this flag in your cpp files or even in the mmp file for example in mmp

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

and in .cpp

#include "../inc/file_strange_supported.h"
#ifdef NEW_API_SUPPORTED
CStrangeApi* api = Api::NewLC();
#else
// ..
#endif

Little Update

Some compilers might support __has_include ( header-name ).

The extension was added to the C++17 standard (P0061R1).

Compiler Support

  • Clang
  • GCC from 5.X
  • Visual Studio from VS2015 Update 2 (?)

Example (from clang website):

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

Sources

Contrary to some claims here and on the internet, Visual Studio 2015 does NOT support the __has_include feature - at least according to my experience. Tested with Update 3.

The rumors may have arisen from the fact that VS 2017 is also referred to as "Version 15"; VS 2015 is instead referred to as "Version 14". Support for the feature seems to have been officially introduced with "Visual Studio 2017 Version 15.3".