2
votes

I need to expand a single preprocessor directive, for example: Having a source file and two headers, I want to expand only one define from one specific header, leaving all other include and define intact.

The main idea is that, given code similar to this:

defs.h:

#define FOO(X,op) int X(int a,int b) { return a op b; }

other_file.h:

#define ONE 1
#define TWO 2
#define THREE 3
#define FOUR 4
#define FIVE 5

main.c:

"file: main.c "
#include <stdio.h>
#include "defs.h"
#include "other_file.h"
FOO(add,+)
FOO(sub,-)
FOO(mul,*)
FOO(div,/)

int main()

{

  printf("%d\n",add(ONE,TWO));
  printf("%d\n",sub(THREE,FOUR));
  printf("%d\n",mul(FIVE,FIVE));
  printf("%d\n",div(25,FIVE));
  return 0;
}

I would have the main.c output with the same includes, but with FOO expanded to the created functions. I known the example is silly, but I intend to run it on a larger code database.

The motivation to do it is to run cccc in functions that are defined within macros. The easiest way to run it is to expand those macros. I also welcome alternative ways to do this.

2
You can't do that (unless some heavy pre-pre-processing of your source files). I would do that with a much more simple regex approach (just write your own preprocessor with script language you prefer, perl?)Adriano Repetti
Ok, I prefer python. The idea to take the "function" (using the regex) to a temporary file, run the preprocessor and put it back in the processed file is too ugly?Cristiano Araujo
No it's OK but I wouldn't overwrite original file. You may rename source files from .C to .raw.C for example and your preprocessor may read all .raw.C files to produce plain .C files. In case you have to change something...Adriano Repetti

2 Answers

1
votes

You can play with the -E, -nostdinc, -nostdinc++ and -fpreprocessed parameters of GCC.

For your example, you can run:

gcc -E -nostdinc -fpreprocessed main.c

And the output would be:

# 1 "main.c"
#include <stdio.h>
#include "defs.h"
#include "other_file.h"
FOO(add,+)
FOO(sub,-)
FOO(mul,*)
FOO(div,/)

int main()

{
  printf("%d\n",add(ONE,TWO));
  printf("%d\n",sub(THREE,FOUR));
  printf("%d\n",mul(FIVE,FIVE));
  printf("%d\n",div(25,FIVE));
  return 0;
}

If the headers are not that complex, like in your example, you can force gcc to preprocess the whole file even with some missing macros. E.g.:

cp other_file.h other_file.h_orig
echo "" > other_file.h
gcc -E -nostdinc main.c

Output:

# 1 "main.c"
# 1 "<command-line>"
# 1 "main.c"
main.c:1:19: error: no include path in which to search for stdio.h
 #include <stdio.h>
                   ^

# 1 "defs.h" 1
# 3 "main.c" 2
# 1 "other_file.h" 1
# 4 "main.c" 2
int add(int a,int b) { return a + b; }
int sub(int a,int b) { return a - b; }
int mul(int a,int b) { return a * b; }
int div(int a,int b) { return a / b; }

int main()

{
  printf("%d\n",add(ONE,TWO));
  printf("%d\n",sub(THREE,FOUR));
  printf("%d\n",mul(FIVE,FIVE));
  printf("%d\n",div(25,FIVE));
  return 0;
}

It will remove the header inclusions, though... and will print you an error on std headers, that goes to stderr instead of stdout.

This works for your small example, but on larger codebase you may face some problems...

Here is a brief summary of the parameters from the manual (of GCC 4.8.2) :

-E: Stop after the preprocessing stage; do not run the compiler proper. The output is in the form of preprocessed source code, which is sent to the standard output.

-fpreprocessed: Indicate to the preprocessor that the input file has already been preprocessed. This suppresses things like macro expansion, trigraph conversion, escaped newline splicing, and processing of most directives.

-nostdinc: Do not search the standard system directories for header files. Only the directories you have specified with -I options.

-nostdinc++: Do not search for header files in the standard directories specific to C++, but do still search the other standard directories.

-1
votes

Our DMS Software Reengineering Toolkit with its C Front End will do this.

DMS provides general purpose program parsing/analysis infrastructure. The C Front End builds on that to provide a full-function C front, complete with C preprocessor.

Normally, the DMS C preprocessor acts just like the standard one: it preprocesses everything, producing a substituted stream of tokens. Unusually, it can be configured to NOT process conditionals (this is all-or-nothing), or to expand only designated macros. In particular, it accepts a custom #pragma that declares that a macro should (not) expand.

It isn't clear to me that this is worth the effort. Yes, a metrics tool might produce more accurate answers in certain places where some macro is heavily used, if you believe that the macro should be non-opaque. If you think the macro is essentially just a funny-looking subroutine, then expanding this macro is like inlining a function body, and you would not do that to compute metrics.