4
$\begingroup$

Thanks to working with Racket recently, I've been spending a lot of time learning about and implementing non-trivial macros. In the process, I have been thinking about ways to apply this skill to my teaching. In particular, I teach students C (in the context of CS50 AP) and would like to show them simple but useful examples of macros in C.

I see the potential benefit as two-fold: 1) it would be helpful for discussing the C pre-processor when I teach the stages of compilation, and 2) it might help students write/understand code with information abstracted away in macros.

What lesson ideas (with code examples hopefully) do you have for introducing students to macros in C?

$\endgroup$
8
$\begingroup$

I introduce C macros as a convenient way of using a single source file that can be used both when debugging code in development and for final delivery, without having to edit and and edit out test/debug code.

I am doing it, in particular, in the context of using the compiler development tools flex and bison, but it would be equally valid when using just C.

For example, say some particular function returns a value:

return(THIS_VALUE);

The student may find it helpful to know what that value is. They could use an interactive debugger, in an IDE (such as Visual Studio), or a stand-alone debugger, such as gdb. Mentioning this makes them aware of the variety of development tools available to professional developers. I also mention that it can also be done without such tools, and code developers need to be aware of these wider varieties of debugging mechanisms. I indicate one of the primitive methods of doing is to add a printf, such as this:

printf("%d\n",THIS_VALUE);
return(THIS_VALUE);

I show, using an example (in flex actually), how this can be helpful. I then explain some of the risks and weaknesses in doing this:

  • We spawn two versions of the code, a production and debug version of the code
  • We have to keep editing the prints in and out and may miss some
  • We may make typos and not actually print the value being returned!

I then illustrate with using the preprocessor, which they may or may not have encountered:

#ifdef DEBUG
printf("The Value is: %d\n",THIS_VALUE);
#endif
return(THIS_VALUE);

I then explain how this can be enabled by using flags in make or gcc or other such tools. The next stage in the explanation shows what can happen when this debug coding happens in many many places:

ONE:
#ifdef DEBUG
printf("The Value is: %d\n",THIS_ONE_VALUE);
#endif
return(THIS_ONE_VALUE);

TWO:
#ifdef DEBUG
printf("The Value is: %d\n",THIS_TWO_VALUE);
#endif
return(THIS_TWO_VALUE);

THREE:
#ifdef DEBUG
printf("The Value is: %d\n",THIS_THREE_VALUE);
#endif
return(THIS_THREE_VALUE);

/* The code does not really look like this. I'm just making paradigms */

I point out how really poor this code is, and how it should be re-factored. This provides an opportunity for discussing re-factoring code and the options available. One option that will immediately occur to the student might be to use some form of procedure or function, like this:

int Trace(int The_Argument)
{
#ifdef DEBUG
printf("The Value is: %d\n",The_Argument);
#endif
return(The_Argument); 
}

ONE:
   return(Trace(THIS_ONE_VALUE));
TWO:
   return(Trace(THIS_TWO_VALUE));
THREE:
   return(Trace(THIS_THREE_VALUE));

Students can see that this re-factoring may have improved the code, but now has inefficiencies that the original did not. It also has become type specific, as types were introduced for the first time (not quite true as the format is typed).

At this point Macros can be introduced as a neat and useful way of conditionally including debug code with the problems of editing statements in and out from the source. Something like this:

#ifdef DEBUG
       #define RETURN(x) printf("Returning " #x ": %d",x); return(x)
#else
       #define RETURN(x) return(x)
#endif

ONE:
   RETURN(THIS_ONE_VALUE);
TWO:
   RETURN(THIS_TWO_VALUE);
THREE:
   RETURN(THIS_THREE_VALUE);

This then enables me to explain about the concepts of Macros versus functions. It also shows C stringification and initiates further discussion of these techniques.

I'm now ready to point them at some more professional discussions of the value of Macros, and in particular I like Jonathan Leffler's Stack Overflow answer.

I reinforce this by putting in the specification of their assignment that their code should implement certain specific debug flags which I will enable when assessing their work. They have to then master the technique to get grade points, in addition to having just working code.

Finally, now they know about Macros, I can talk about inline functions, how a Macro can be used to define one, based on the examples above. It also enables me to talk about compiler optimisations and how the compiler might introduce inlining itself without explicit macros from the programmer.

(This is part of a compilers course so such topics will be core knowledge for them).

$\endgroup$
  • 1
    $\begingroup$ We're always glad to get more teachers here. This is a very thorough description of the lesson. Nicely done, and welcome to Computer Science Educators! $\endgroup$ – Ben I. Jul 19 '17 at 14:31
  • 1
    $\begingroup$ This is much better than my example. :-) Welcome to CSE! $\endgroup$ – Ellen Spertus Jul 19 '17 at 16:08
3
$\begingroup$

It is useful to create a macro to compute the maximum operation. The advantages of a macro over a function are:

  • performance (which we all know is important to C programmers, although they can use an inline function)
  • polymorphism (C won't allow you to have a single or multiple functions named max for different numeric types)

The implementation turns out to be nontrivial. The first version is likely to be:

#define MAX(a,b) (a > b ? a : b)

This has two problems. The first is the lack of parentheses, which can lead to incorrect order of operations. For example,

MAX(c && d, 1)

would become

(c && d > 1 ? c && d : 1)

whose meaning is the same as:

(c && (d > 1)) ? (c && d) : 1

This problem can be fixed by changing the definition to:

#define MAX(a,b) ((a) > (b) ? (a) : (b))

An additional problem is that evaluating a and b may have side effects. Consider:

MAX(++c, ++d)

This expands into:

((++c) > (++d) ? (++c) : (++d))

As described on our sister site, a better version is:

#define MAX(a,b) ({
    typeof (a) _a = (a);\
    typeof (b) _b = (b); \
    _a > _b ? _a : _b; })

This, of course, is ugly and will not work if you have variables named _a and _b.

I'm afraid I've done the opposite of what you've requested and provided an argument against C macros. (I use the above argument before teaching Scheme's hygienic macros so students appreciate them.)

$\endgroup$
  • 1
    $\begingroup$ Thank you for these examples! That latter point about Scheme is something the professor in the programming languages MOOC I'm taking brings up as well. Part of why I want to show macros is because of how awesome they have been to learn in Racket with hygiene being a key concept thereof. I may just be full of wishful thinking when it comes to C's macros... $\endgroup$ – Peter Jul 19 '17 at 6:14
2
$\begingroup$

If you look at the header files for the stdio library (maybe this was true long ago and is no longer, but I can't help with that), you will find how the FILE structure is implemented. It is fascinating to see how the pros create something low-level like this.

There are macros to get and put characters, and manage a buffer. This is more efficient than using a function. When I was building a database transaction server (like a web server), I knew I would need a way to get and put chars on a socket connection, and I knew it needed to be efficient. So, I basically copied the macros out of my favorite version of FILE in the stdio and renamed my structure WIRE.

One thing I learned is that it is difficult to fit a complex macro in to the syntax requirements of an expression. This can be solved using the Comma Operator in place of the semicolon. This is why I say, "I knew I had mastered C when I found a vital use for the comma operator." It is a great line at parties, haw haw

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.