3
votes

I was wondering how standard code complexity metrics (e.g., LOC, McCabe cyclomatic complexity, Halstead metrics, etc) are computed in the face of ifdefs in C/C++ code.

If ifdefs are ignored, syntax errors (or even type errors) may arise, such as in:

  1. #ifdef FOO
  2. for(i = 0; i < x; i++) {
  3. #else
  4. for(i = 0; i < y; i++) {
  5. #endif
  6. printf(...)
  7. }

If lines 1, 3 and 5 are ignored, the resulting code would have one loop inside the other, and a missing '}' (thus the syntax error).

If #ifdefs are taken into account, then one would need a single resulting measure per variant (in this case, one when FOO is defined, and another one for the case when FOO is not defined). However, such an approach can easily explode in practice.

Looking at http://manpages.ubuntu.com/manpages/natty/man1/pmccabe.1.html, the manpage reports that:

Parsing pmccabe ignores all cpp preprocessor directives - calculating the complexity of the appearance of the code rather than the complexity after the preprocessor mangles the code. This is especially important since simple things like getchar(3) expand into macros which increase complexity.

But, as I said, this approach may lead to code errors to appear, which in turn, may hinder the calculation process or result in an incorrect value.

How do tools overcome this problem (if they overcome at all)?

Regards.

3

3 Answers

2
votes

If you rewrite that construct as

#ifdef FOO
# define LOOPEND x
#else
# define LOOPEND y
#endif

or something like that, there should be no problem.

If you don't want to do that, and your complexity checker doesn't understand the preprocessor, you can run it on the output of the preprocessor, both with FOO defined and with FOO undefined, then take the max of the values it produces to get a lower bound on the actual complexity. With GCC, the preprocessor is invoked with gcc -E; other compilers will have a similar option.

(Of course, if you have a lot of this CPP wizardry, you're left with an exponential number of possibilities. But that's the price you pay for using the preprocessor; the same applies to testing.)

1
votes

Many of the metric calculators do not use full or conforming parsers, instead they do (maybe) the first couple of phases of translation and then look for keywords.

Since they are not looking at the full syntax issues like you describe do not block them. Howevery they do become fragile in other ways, in fact I have seen cyclo-2.0 fail compleatly on solid production code.

0
votes

I would have thought that in a larger project (rather than a 20 line "experiment") the amount of #ifdef and similar that affect the ACTUAL complexity of the code would be rather minimal - you'd hope that most of those sort of expressions are in isolated units that aren't affecting the overall score too much.

But yes, if you have a small set of code with large amounts of #ifdef and complexity within the #ifdef parts, you could get a large error. Manual inspection of the code would be able to tell you this.