Warning! This site is under development.

EBoard 10: The C Preprocessor + Macros + More

  • This class will not be recorded.

Approximate overview

  • Administrative stuff
  • Quick Macro review
  • The C preprocessor
  • Header design
  • An application: Verifying expectations
  • Concatenation and metaprogramming

Administrivia

C Macros

  • Key idea: Programmatic text replacement
  • During the preprocessing stage, we replace the macro “call” with the macro body, substituting the text of a macro’s actuals in for the formals.
  • E.g.,
    • Definition: #define MAC(CROW) fprintf (stderr, "%s %s\n", CROW, CROW)
    • “Call”: MAC("Sim, uh");
    • Result fprintf(stderr, "%s %s\n", "Sim, uh", "Sim, uh");
  • Observed issue: Because they do textual substitution, macros can sometimes create unexpected behavior if we do not parenthesize appropriately.
    • #define SQUARE(X) ((X)*(X))
    • And you thought there were a lot of parens in Scheme!

Detour, More Fun with Macros

#define DUMB_ZERO(X) ((X)-(X))
  printf ("DUMB_ZERO(val++) = %d\n", DUMB_ZERO(val++));

The C Preprocessor

Reminder; The first step of compilation. Primarily does easy textual transformations.

The big four

  • #include (brings in other files, typically .h files, but also .c files)
  • #ifdef#else#endif (conditionally includes code in the file)
    • historically used for different architectures
    • Can also be used for adding debugging or verbosity flags
  • #define (for both constants and macros)
  • Strips all the comments, which are useless.

Bad example of architecture

#ifdef MC68000
   #define intbits 8
   #define shortbits 4
#endif
#ifdef x86
   #define intbits 16
   #define shortbits 8
#endif
#ifndef intbits
   #define intbits 2
   #define shortbits 1
#endif

Bad example of debugging

#ifdef DEBUG
   fprintf (stderr, "Got here!\n");
#endif

A common fourth

  • #if#elif#else#endif

And beyond

  • _Pragma (formerly #pragma)
  • #line
  • Some random stuff (e.g., #ident)

What else did you learn from skimming?

  • Not much. Sam’s readings confused us so much that we forgot.

Header design

  • It’s bad to include the same header twice. Why? It will give you the same defniition twice, which the C compiler doesn’t like.
  • Sometimes it happens indirectly.
  • How do we avoid that?

Historical solution

#ifndef __FILE_H__
#define __FILE_H__

// CODE

#endif __FILE_H__

Stringification

Hey, it’s better than stringizing.

  • #PARAM converts a parameter into a string.
  • Simplified variant of the example from the manual
    • #define VERIFY(EXP) if (! (EXP)) fprintf (stderr, "Failed to verify that: %s\n", #EXP)
  • Sample usage
    • Input VERIFY(x > 0)
    • Output if (! (x > 0)) fprintf (stderr, "Failed to verify that: %s\n", "x > 0")

Improving the example

I don’t know about you, but I’m uncomfortable with the lack of braces around the body.

A proposed solution:

#define VERIFY(EXP) if (! (EXP)) { fprintf (stderr, "Failed to verify that: %s\n", #EXP); }

A Problem

Input

VERIFY(x > 0);

Output

if (! (x > 0)) { fprintf (stderr, "Failed to verify that: %s\n", "x > 0"); };

That makes us a bit uncomfortable.

A worse problem

Input

if (TODAY_IS_THURSDAY)
  VERIFY(x > 0);
else
  x = max(x, 1);

Output

if (TODAY_IS_THURSDAY)
  if (! (x > 0)) fprintf (...); 
else
  x = max(x, 0);

Normal if model

if (TEST)
  exp;
else
  exp;

Note

if (TEST)
  { exp; };
// No else permitted

The traditional “solution” (aka “kludge”)

Wrap the code in a do { ... } while (0).

For example,

#define VERIFY(EXP) \
  do { if (! (EXP)) { fprintf (stderr, " Failed to verify that %s\n", #EXP); } } while (0)

Now you can safely add a semicolon and use it in any situation.

Further improvements

Further improvements …

#define VERIFY(EXP) \
  do                                                            \
    {                                                           \
      if (! (EXP))                                              \
        {                                                       \
          fprintf (stderr, " Failed to verify that %s on line %d of %s\n",   \
                           #EXP, __LINE__, __FILE__);           \
        }                                                       \
     }                                                          \
  while (0)

Wasn’t that fun?

Note that you can play with __LINE__ and __FILE__ using #line

Why use Macros?

  • You can get some behaviors, like the stringification, that aren’t possible with functions.
  • Historically, it’s more efficient to inline code than to do a recursive call. Smart compilers deal with that now.

Concatenation

The ## operation connects the text of a macro parameter along with some other text.

#define RECURSIVE_ARRAY_FUNC(NAME, TYPE, CODE)                          \
  void NAME(TYPE array[], int n) { NAME ## _kernel (array, n, 0); }     \
  void NAME ## _kernel(TYPE array[], int n, int pos) {                  \
    if (< pos n) {                                                      \
      CODE;                                                             \
      NAME ## _kernel(array, n, (+ pos 1));                             \
    }                                                                   \
  }
RECURSIVE_ARRAY_FUNC(increment, int, array[i] = array[i] + 1)

Isn’t metaprogramming fun?

  • metaprogramming: Writing code to write code

Challenge: Write ARRAY_MAP(ARRAY, FUNC, N)

for (int i = 0; i < n; i++) { a[i] = FUNC(a[i]); }

E.g.,

ARRAY_MAP(ints, square, numints)

would give

for (int i = 0; i < numints; i++) { ints[i] = square(ints[i]); }

We don’t need the type, since a[i] = FUNC(a[i]) should work for any type.

Or write ARRAY_BUILD(ARRAY, THUNK, N)

I.e.,

ARRAY_BUILD(ints, x++, numints)

would give

for (int i = 0; i < numints; i++) { ints[i] = x++; }

Bring your answers to the next class.