14

PrecompiledHeader.h:

#include "stdio.h"

main.cpp:

#include "PrecompiledHeader.h"
#include "stdio.h"

int main()
{
   return 123;
}

Creating precompiled header:

clang -x c++-header PrecompiledHeader.h -o PrecompiledHeader.pch

Running clang preprocessor on main.cpp:

clang main.cpp -include-pch PrecompiledHeader.pch -E 

This currently outputs many many many pages of output (stdio.h). However, what I would like to get is only the main function, and have the contents of PrecompiledHeader.h be omitted from the output.

Is there any way to make clang do this? (if someone knows the answer to the same question for visualstudio's cl.exe I'd love to know about it too :)

Lucas Meijer
  • 4,424
  • 6
  • 36
  • 53
  • "I would like to get is only the main function, and have the contents of PrecompiledHeader.h be omitted from the output." This is unclear. If you omit (just) the contents of `PrecompiledHeader.h` then you'll get the main function *plus* the contents of `stdio.h`. – Mike Kinghan Jun 07 '16 at 11:46

3 Answers3

4

Hardly such a way exists or will be built into clang (or any other C++ compiler) in the future. The problem is that precompiled headers are used to speed up the compilation step which is usually significantly more expensive than the preprocessing step. As a result, if you are going to just preprocess your source, the precompiled header will simply not be used. One way that has a negligibly small chance of producing what you want is to use the -include option instead of the -include-pch option (note that you must specify the *.h rather than the *.pch file to it):

clang main.cpp -include PrecompiledHeader.h -E

The guaranteed way of solving your problem is to pipe the preprocessed output through a simple filter program that looks at the # line "file" directives and removes text coming from #includeed files:

clang main.cpp -E|remove_included_code

remove_included_code can be easily implemented in python, bash, C/C++, etc. Here is a C++ implementation (C++ was chosen so that it can be most easily used under Windows):

remove_included_code.cpp

#include <iostream>
#include <cstdlib>

using namespace std;
typedef std::string Str;

bool isLineDirective(const Str& line)
{
    return line.size() >= 3
        && line[0] == '#'
        && line[1] == ' '
        && isdigit(line[2]);
}

Str getFile(const Str& line)
{
    const Str::size_type start = line.find_first_of('"') + 1;
    const Str::size_type end = line.find_first_of('"', start);
    return line.substr(start, end - start);
}

int main()
{
    Str line;
    getline(cin, line);
    if ( !isLineDirective(line) ) {
        cerr << "Error: Input must start with a '# line \"file\"' directive\n";
        exit(1);
    }

    const Str startFile = getFile(line);
    Str currentFile = startFile;
    while ( getline(cin, line) ) {
        if ( isLineDirective(line) )
            currentFile = getFile(line);
        else if (currentFile == startFile )
            cout << line << endl;
    }

    return 0;
}

The advantage of this solution is that it will work with all compilers.

Leon
  • 31,443
  • 4
  • 72
  • 97
  • 1
    Out of all languages you chose cpp for remove_included_code? – Daniel Jun 12 '16 at 13:48
  • 1
    @Dani I did it for a reason. Though indeed absolutely not the best language for text processing, a simple program written in C++ has the advantage of requiring least dependencies for running. This matters, if OP needs to run it under Windows, where python and bash are not 1st class citizens. – Leon Jun 12 '16 at 13:56
1

edit: I don't see anything special about precompiled headers in this case - any means of conditionally preventing the expansion of an #include will do.

I achieve this for standard header includes by wrapping in an ifdef, such that the preprocessed output contains #include "foo" but not the contents. For example,

#ifdef PRECOMPILE
#define TMP #include <stdio.h>
TMP
#undef TMP
#else
#include <stdio.h>
#endif

Then pass -DPRECOMPILE along with -E.

The preprocessed output then contains the unexpanded #include. I find this more useful than the simpler alternative of wrapping the #include in #ifdef when reading the preprocessed output.

Primarily though, this means I can run macro-heavy code through the preprocessor then clang-format before compiling as normal, for a far improved debugging experience.

Jon Chesterfield
  • 2,251
  • 1
  • 20
  • 30
0

With gcc, you can use -fpch-preprocess.

This option allows use of a precompiled header (see Precompiled Headers) together with -E. It inserts a special #pragma, #pragma GCC pch_preprocess "filename" in the output to mark the place where the precompiled header was found, and its filename. When -fpreprocessed is in use, GCC recognizes this #pragma and loads the PCH.

This option is off by default, because the resulting preprocessed output is only really suitable as input to GCC. It is switched on by -save-temps.

You should not write this #pragma in your own code, but it is safe to edit the filename if the PCH file is available in a different location. The filename may be absolute or it may be relative to GCC’s current directory.

This flag is not available in clang. Instead, you can add -Xclang -pch-through-hdrstop-create which was inspired by msvc, but the usage is similar to what you get with -fpch-preprocess: the compiler, when invoked with -include-pch PrecompiledHeader.pch -Xclang -pch-through-hdrstop-use main.cpp.ii, will consider the first lines in main.cpp.ii as coming from the precompiled header, and will not complain about duplicate declarations, etc.

Unfortunately, this behavior is not perfect, e.g. on my Mac there is a clash between the declaration

int getchar_unlocked(void);

that comes from line 300 of …/MacOSX.sdk/usr/include/stdio.h and

#define getchar_unlocked()  getc_unlocked(stdin)

that comes from line 322 in the same system header file.

For me, the approach proposed by @Leon worked better. I added a twist to it: when compiling the PCH, I add -include my_pragma.h, which is a one-line file:

#pragma GCC pch_preprocess

My parser (an analog of remove_included_code) is looking for this pattern and throws away everything before it. This can be easily implemented as awk script.

Alex Cohn
  • 56,089
  • 9
  • 113
  • 307