CSC161 2010F Imperative Problem Solving

Laboratory: The C Preprocessor and Macros

Summary:

Prerequisites: Familiarity with basic C. Familiarity with Linux.

Preparation

a. Create a directory for this lab. I would recommend CSC161/Labs/Preprocessor.

b. In that directory, create the following C program and call it sample1.c.

#define SIZE 5

int
main ()
{
  int i;
  int a[SIZE];

  a[0] = 0;
  for (i = 1; i < SIZE; i++)
    a[i] = 2*i + a[i-1];

  return 0;
} // main

Exercises

Topic A: Naming Constants

Exercise 1: Simple Definitions

a. Let's discover what the C preprocessor does with the simplest form of #define. Type the following command and compare the output to that file. Be prepared to explain what the preprocesor has done.

cc -E sample1.c

b. Right now, the program does not do much interesting. Let's add a printf statement. (Do not add the corresponding #include yet.) Add the following before the return statement.

  for (i = 0; i < SIZE; i++)
    printf ("a[%d] = %d\n", i, a[i]);

c. What do you expect to have happen if you preprocess the new file?

d. Check your answer experimentally.

e. What do you expect to have happen if you compile and run the program?

f. Check your answer experimentally.

g. Replace the line that defines SIZE with

#define SIZE 3+4

h. What do you expect to happen if you preprocess the revised file?

i. Check your answer experimentally.

j. What do you expect to have happen if you compile and run the program?

k. Check your answer experimentally.

l. Restore the #define with

#define SIZE 5

Exercise 2: Basics of #include

As you know, the C compiler really likes to have functions predeclared. As you also know, those predeclarations often appear in header files, files with a suffix of .h. In fact, you've probably just seen some warning messages because you failed to include <stdio.h>.

a. Add the #include line to your program.

b. What do you expect to have happen if you now compile and run your program?

c. Check your answer experimentally.

d. What do you expect to see when you preprocess the file?

e. Check your answer experimentally.

Exercise 3: Where Does That Semicolon Go, Anyway?

By now, you're probably used to putting a semicolon at the end of every statement. You may have noted that we did not put a semicolon at the end of our #define. Let's try adding one.

a. Replace the #define in sample1.c with

#define SIZE 5;

b. What do you expect to have happen if you now compile and run your program?

c. Check your answer experimentally.

d. What do you expect to see when you preprocess the file?

e. Check your answer experimentally.

Exercise 4: More Ways to Define Constants

Let's explore other ways to think about associating values with names.

a. Remove the #define line from sample1.c.

b. What do you expect to see when you preprocess the file?

c. Check your answer experimentally.

d. What do you expect to have happen if you now compile and run your program?

e. Check your answer experimentally.

f. It turns out that you can also define names on the command line with -DNAME=VALUE. Let's try setting SIZE to 10 with

cc -DSIZE=10 -o sample1 sample1.c

g. Run the newly compiled version of sample1 to see if we get the expected result.

h. Discuss with a neighbor why this technique might be useful. Present your collective answer to the class leader or class mentor before moving on to the next exercise.

Exercise 5: Multiply-Defined Constants

a. Reinsert the following line at the top of sample1.c.

#define SIZE 5

b. What do you expect to have happen when you try to compile the program using the following command?

cc -DSIZE=10 -o sample1 sample1.c

c. Check your answer experimentally.

d. As you no doubt have discovered, our C compiler doesn't particularly like it when you try to define a constant twice. Can we allow the programmer to specify a size during compilation if she wants to, but use a default size if she does not specify one during compilation? Certainly. We can tell the preprocessor to check whether a constant is defined before defining it. Replace the definition in your code with

#ifndef SIZE
#define SIZE 5
#endif

e. Verify that the program uses a size of 5 when compiled in the normal way, and a size of 10 when compiled with -DSIZE=10.

Topic B: Macros

Exercise 6: Maximal Macros

As you may recall, the definition of the max function is fairly simple. (We've used a slightly different name to remind ourselves that maxf is a function.

int
maxf (int x, int y)
{
  return x > y ? x : y;
} // maxf

a. Write a test program using maxf that verifies that the larger of 7 and 11 is 11. (Yes, the answer is obvious, but it's good to check anyway.) While good practice dictates using separate files for maxf and your test, put them all in one file anyway. Call your program max1.c.

b. Often, as C programmers, we are tempted to turn such simple code into a macro. Since macros are inlined, we don't need the return.

#define MAXM(X,Y) X > Y ? X : Y

Add this macro to max1.c.

c. Replace the call to maxf with a call to MAXM.

d. What do you expect to see when you run the preprocessor on max1.c?

e. Check your answer experimentally.

f. Do your expect the results from the revised max1.c to differ? If so, how?

g. Check your answer experimentally.

Exercise 7: A Squared Detour

K&R caution us about a danger implicit in the following definition.

#define SQUARE(X) X*X

Let's explore that danger.

a. Create a program, sample2.c, that uses this macro to compute the square of 5. Print the result. Do you get the output you expect?

b. Modify your program so that it instead computes the square of 2+3. Do you still get the result you expect?

c. You should have discovered that you get a very different result than you expected. Why? Let's use the preprocessor to find out. What does the call to SQUARE turn into in the following?

cc -E sample2.c

d. Given your analysis from the previous step, fix the definition of SQUARE.

Exercise 8: More Manipulations of Maxima

Let's try a few more examples to make sure that our maxf function and MAXM macro work correctly. Here are a few tests to add to your list.

int errors = 0;

void
check3 (int a, int b, int c, int expected)
{
  int result = maxf (maxf (a, b), c);
  if (result != expected)
    {
      printf ("For max (max (%d, %d), %d), expected %d, got %d.\n",
              a, b, c, expected, result);
      ++errors;
    }
} // max3

int
main ()
{
  // Check various permutations
  check3 (1, 2, 3, 3);
  check3 (1, 3, 2, 3);
  check3 (2, 1, 3, 3);
  check3 (2, 3, 1, 3);
  check3 (3, 1, 2, 3);
  check3 (3, 2, 1, 3);

  return errors;
} // main

a. Determine whether or not maxf successfully meets these additional tests.

b. Determine whether or not MAXM successfully meets these additional tests. (Note that you'll need to replace each instance of maxf in check3 with MAXM.

c. As K&R implicitly suggest, one reason we see problems like the preceding is that textual substitution can raise issues of precedence in the subsituted expression. They give a somewhat different definition of MAX, using lots and lots of parenthesis to guarantee that there is no ambiguity.

#define MAXM(X,Y) ((X) > (Y) ? (X) : (Y))

Verify that this definition passes the tests.

Exercise 9: The Danger of Semicolons, Revisited

As we saw earlier, it can be dangerous to put a semicolon at the end of a constant definition. Should it make a difference if we put one at the end of a macro definition?

a. What do you expect to have happen if you add a semicolon to the definition of MAXM given above?

b. Check your asnwer experimentally.

Exercise 10: Even More Fun with Macros

K&R also raise an issue with their version of MAX. Let's explore their concern.

a. What value do you expect a, b, and c to have in the following expression? Recall that ++var adds one to var and gives back the new value of var.

  int a = 5;
  int b = 7;
  int c = maxf (++a, ++b);

b. Check your answer experimentally.

c. We would expect MAXM to give the same results. Determine experimentall whether or not it does.

d. Be prepared to summarize what went wrong in this case.

e. How might you repair this problem?

Topic C: Why Macros?

Exercise 11: Why Use Macros?

Given the potential problems that we've just observed in using macros, why would one ever use macros? It turns out that there are a wide variety of reasons. Working with a neighbor, come up with at least one (and preferably at least two) reasons that we might choose to use macros instead of functions.

Discuss those reasons with your instructor or class mentor before going on to the next exercise.

Exercise 12: Some Empirical Comparisons

Because C programmers often care about making the best use of resources, and there is a slight overhead involved in each function call. Let's explore that overhead.

a. Create a new program, max3.c, that includes your definitions of maxf and MAXM and that has the following main procedure.

int
main ()
  int i;
  int x = 0;

  for (i = 0; i < 10000000; i++)
    x = maxf (x, i);

  return 0;
} // main

b. What does this code seem to do? Why might it be useful for examining the overhead used in a function call?

c. Compile your program and then run it with the following command, which tells you how much computer time has been used.

time ./max3

d. Change the call from maxf to MAXM, recompile, and retime the program. How much time was saved? Is that a lot or a little?

Exercise 13: A General Answer

We've written maxf to find the maximum of two integers. However, its name might lead someone to believe that it finds the maximum of two floating point values.

a. What do you expect maxf to do when given float values as parameters. For example, what output do you expect for the following?

  float f = maxf (3.5, 2.3);
  printf ("The max is %f\n", f);

b. Check your answer experimentally.

c. What do you do MAXM to do when given float values as parameters?

d. Check your answer experimentally.

e. Why might someone consider this an advantage of using macros?

Exercise 14: Testing

One of the best reasons to use Macros is that macros give you really fun capabilities, such as the ability to turn some code into a string. For example, here is a macro you might find useful.

#define TEST(EXP,RESULT) if (EXP != RESULT) { ++errors; printf ("Did not get expected result for %s.\n", #EXP); }

Check whether the macro works.

Topic D: Including Files

Exercise 15: The Structure of Headers

Here's a simple header file that defines a constant and a function.

#define MAX_VALUES 1024

int fun (int x);

a. Put that code in the file header.h.

b. Here's a library file that uses that header. Save it as library.c.

#include "header.h"

int 
fun (int x)
{
  return x;
  // wasn't that fun?
} // fun

c. Verify that you can make library.o.

d. What do you expect to have happen if you inadvertantly include header.h twice?

e. Check your answer experimentally.

f. Believe it or not, but multiple header inclusion is a real problem. Why? Because many headers include other headers, so while you don't think you're including the same header multiple times, you actually are. How do we get around this problem? You've already seen the typical strategy.

Let's try. At the top of header.h, add

#ifndef __HEADER_H__
#define __HEADER_H__

At the bottom of header.h, add

#endif

g. Now, see what happens when you include header.h multiple times. (You'll find it useful to look using the preprocessor.)

 

History

Thursday, 14 October 2010 [Samuel A. Rebelsky]

  • Started wrting

Friday, 15 October 2010 [Samuel A. Rebelsky]

  • Finished writing.
  • First released version.

 

Disclaimer: I usually create these pages on the fly, which means that I rarely proofread them and they may contain bad grammar and incorrect details. It also means that I tend to update them regularly (see the history for more details). Feel free to contact me with any suggestions for changes.

This document was generated by Siteweaver on Fri Oct 15 11:45:49 2010.
The source to the document was last modified on Fri Oct 15 11:45:47 2010.
This document may be found at http://www.cs.grinnell.edu/~rebelsky/Courses/CSC161/2010F/Labs/preproc-lab.html.
A PDF version of this document may be found at http://www.cs.grinnell.edu/~rebelsky/Courses/CSC161/2010F/Labs/preproc-lab.pdf

Samuel A. Rebelsky, rebelsky@grinnell.edu

Copyright © 2010 Samuel A. Rebelsky. This work is licensed under a Creative Commons Attribution-NonCommercial 2.5 License. To view a copy of this license, visit or send a letter to Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA.