4

We have several projects in development sharing the same codebase. Certain pieces of code are only relevant to one or other of those projects.

We have a couple of requirements:

The first requirement is that we want our final releases not to compile in code from the other projects.

One solution (the one we like) is to use the preprocessor to remove that code: (this is what we do in some places).

#if defined PROJECT1
{
    // some code
}
#endif

The second requirement is, that while we are developing, it is helpful to make sure that a change of code still works across all projects, so we would like to compile ALL the project code at once, and be able to switch between projects without a recompile - so in other places in our code, we use a variable to determine the project instead:

if (project == 1)
{
    // some code
}

What I'd like to be able to do is to combine the benefits of both - code which in some situations (let's say determined by a #define REMOVECODE) is not included in the final exe at all, but in other situations (determined by the non-definition of the REMOVECODE define) to include the code in the compiled .exe

One more thing - sometimes we have code which exists in a couple of projects, so the solution would need to handle tests like "if project == 1 || project == 2"

I'm thinking it would look something like the following (this doesn't work because I don't think you can nest preprocessor directives), but I'm not sure if it is even possible with macros. Maybe there's some kind of template solution?

#ifdef REMOVECODE
    #define BEGINTEST #if
    #define ENDTEST   #endif
    #define CONDITION1 defined PROJECT1
#else
    #define BEGINTEST if
    #define ENDTEST
    #define CONDITION1 project == 1
#endif

BEGINTEST(CONDITION1)
{
    // some code
}
ENDTEST

If anyone can help out, I'd be much obliged.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Tim Gradwell
  • 2,312
  • 5
  • 24
  • 25
  • 1
    Do you mean `project == 1`? Why not say `#define CONDITION1 (project == 1)` and then use it as `BEGINTEST CONDITION1`? – Kerrek SB Jul 06 '11 at 15:27
  • Generally speaking, if the test for an `if` can be evaluated at compile-time, the optimizer will remove the if, so just using the "run-time" `if` with and without optimization may do what you want. Beware of the common mistake of using `=` when you mean `==`. – andrewdski Jul 06 '11 at 15:28
  • I'll think on it, but first off, be advised that 'project = 1' is assignment, not comparison. If you write "if(project = 1) {...} else {...} the else branch will never be taken. – David Seiler Jul 06 '11 at 15:28
  • why not use the '-D' compiler flag (for gcc) and define more than one project at once? – HRÓÐÓLFR Jul 06 '11 at 15:31
  • re 'project = 1' : it's only pseudocode :-) but thanks - I've edited it. – Tim Gradwell Jul 06 '11 at 15:31

6 Answers6

4

If the condition in the test is a compile-time constant, any half-decent optimizing compiler will remove the dead code completely from the object file.

So something as simple as this should work fine:

#ifdef REMOVECODE

#ifdef PROJECT1
#define CONDITION1 1
#else
#define CONDITION1 0
#endif

#else
#define CONDITION1 project == 1
#endif

...

if (CONDITION1)
{
    ...
}

Run your compiler with -S (or equivalent) and look at the generated assembly to confirm.

Nemo
  • 70,042
  • 10
  • 116
  • 153
  • we set the project on the command line, so I'm afraid it isn't compile-time constant - good answer though. – Tim Gradwell Jul 06 '11 at 15:51
  • That works nicely, but cannot remove the complete definition of a function -- it can only neutralize the body of a function. – Jonathan Leffler Jul 06 '11 at 15:55
  • @Tim: I do not understand. In the case where you want to remove the code completely, it _has_ to be a compile-time constant... Your own proposal assumes that `REMOVECODE` is either defined or not, and when it is, that `PROJECT1` is either defined or not. Same as mine. – Nemo Jul 06 '11 at 16:15
  • @Nemo: Ok, so in a simple test case, I can look at the assembly and your answer works fine - if I define REMOVECODE, the generated assembly is actually smaller. However, when I put it in our real project, the saving isn't so great. Manually deleting all the code that isn't required for one project saves us about 15k. Changing our project ID to a const int only manages to save us 300 bytes. So I'm still looking for a solution. – Tim Gradwell Jul 07 '11 at 08:19
  • @Tim: A global `const` variable is not really a compile-time constant, at least not to most compilers... In order to make this work, you actually need something like `#define CONDITION1 0` so that the `if` is directly testing the constant `0`. – Nemo Jul 07 '11 at 17:14
  • @Nemo: Ok, I changed to a #define and it worked - 15k saved - thanks! – Tim Gradwell Jul 08 '11 at 07:34
  • @Nemo that really sounds great !!! What I need is, possible to load any macro by assigning the values dynamically like from NSUserDefaults for instance logging. If user disable logs from screen, there should be no logs else... it can print logs, least bother about Debug/Release mode. I have " #define NSLog(__FORMAT__, ...) TFLog((@"%s [Line %d] " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) ", I need to check the condition based on NSUserDefaults. Please let me, how I can achieve this ? – Ajay Sharma Jan 21 '14 at 12:19
1

It won't work; you can't generate 'pre-processor directives' from inside macros, etc.

Or, more accurately (since you can generate what looks a pre-processor directive, as shown), if you do generate what looks like a pre-processor directive via a macro, a standard-conforming preprocessor will not recognize it as a directive.

The same comments apply to C and C++:

ISO/IEC 14882:1998 (C++ Standard)

16.3.4 Rescanning and further replacement [cpp.rescan]

¶3 The resulting completely macro-replaced preprocessing token sequence is not processed as a preprocessing directive even if it resembles one.

ISO/IEC 9899:1999 (C Standard)

6.10.3.4 Rescanning and further replacement

¶3 The resulting completely macro-replaced preprocessing token sequence is not processed as a preprocessing directive even if it resembles one, ...

(The ellipsis goes on to mention the C99 _Pragma feature.)

Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
1

How about having another define saying "build everything". And then use

#if defined(PROJECT1) || defined(BUILDALL)
    ...
#endif

Or to avoid adding || defined(BUILDALL) everywhere you could stick this somewhere where it will be visible:

#ifdef BUILDALL
    #define PROJECT1
    #define PROJECT2
    //...
#endif

Or just compile with all project macros defined

sbk
  • 9,212
  • 4
  • 32
  • 40
0

And what's wrong with having a nightly automated sanity that rebuilds all projects and runs an automated test suite on each one and you'll know if something is broken?

What I see here will likely end up in a #ifdef clusterfuck or additional code complexity + errors for nothing.

David
  • 3,324
  • 2
  • 27
  • 31
  • we are running continuous integration - but we want to check our code builds before we commit it, so as not to disrupt any other developers. – Tim Gradwell Jul 06 '11 at 15:38
0

Refactor your design. Break the code into smaller, independent libraries. Only link in what you need to each project. Otherwise, you're going to end up with a very complex mess.

Rob K
  • 8,757
  • 2
  • 32
  • 36
0

Defines to control actions either define these or not If HARD_P1 is not defined block of code are excluded. So a function can be taken out of the compiled result. If SOFT_P1 is defined lines of code are included. If SOFT_P1 is not defined these lines are commented out.

#define HARD_P1
#define SOFT_P1

Then

#ifdef HARD_P1
#define P1
#ifdef SOFT_P1
#define USEP1
#else
#define USEP1 /##/
#endif
#endif

Note the use of /##/, the preprocessor will replace USEP1 with /##/ paste the / together to produce // the // will then comment out the rest of the line. So this can be used to exclude individual lines of code.

Code

#ifdef P1
void test()
{
USEP1 if(project==1)
    {
    }
}
#endif
QuentinUK
  • 2,997
  • 21
  • 20
  • could you explain your answer a little more? what do I do if I want the code in the braces to compile away? what do I do if I want the code to be conditional on project being equal to 1? what does //##// do? – Tim Gradwell Jul 06 '11 at 21:22
  • Oops, replace //##// with /##/. For the code in braces to compile define HARD_P1 (to include the function) The code in braces will then compile. If SOFT_P1 if not defined the code in braces will always execute when the function is called. If SOFT_P1 is defined the code in braces will execute only when project==1. – QuentinUK Jul 09 '11 at 14:31