Search code examples
cdebuggingc-preprocessorc89

A way around defining new variables in function calls


I have a function dangerous(GEN x) which is called frequently in my code, where GEN is a typedef. For debugging purposes, I would like to add checkSafe to all instances of this function, something like

#ifdef DEBUG
  #define dangerous(x) GEN __x = (x); if(checkSafe(__x)) dangerous(__x)
#endif

but I'm concerned that this might not work as intended. What's the right way to do something like this? The function is used too often to instrument each use individually and it is not desirable to check outside debug mode (for various reasons).


Solution

  • Things to be aware of / careful about:

    1. Using a macro and a function with the same name at the same time. While it can produce valid C, you'll have to 1) take extra precautions to avoid unwanted expansion (either always define the function before the macro, or enclose the function name in parentheses at definition time) and 2) double check that every use of the function also includes your instrumenting code.

    Solution: rename the original function into something like _dangerous.

    1. Using the macro in various situations:
      • in an if with a single statement: if (foo) dangerous(x);
      • around an else from the parent if: if (foo) dangerous(x); else bar();
      • when leaking variables into the parent namespace can break things: GEN __x = 5; dangerous(__x);.

    Solution: enclose the macro in a construct like do { ... } while(0).

    1. You must take into account any side effects at copy time, like resource allocation or CPU intensive operations (since GEN is a typedef, this is likely not a concern).

    Lastly, you may also want to complain when checkSafe fails, e.g. by logging an error message, or even aborting the program.

    Putting the above together, you would instrument the function like this:

    #ifdef DEBUG
      #define dangerous(x) do { \
        GEN __x = (x);          \
        if (checkSafe(__x))     \
          _dangerous(__x);      \
        else                    \
          complainAbout(__x);   \
      } while(0)
    #else
      #define dangerous _dangerous
    #endif
    
    1. If dangerous() returns a value (e.g. int) that you want to use.

    Solution: Define a function to instrument your original function and pass the return value up:

    #ifdef DEBUG
      static inline int dangerous(GEN x) {
        if (checkSafe(x))
          return _dangerous(x);
        complainAbout(x);
        return ERROR_CODE;
      }
    #else
      #define dangerous _dangerous
    #endif