Skip to main content

The Preprocessor

Exercises

Question 14.1

Write parameterized macros that compute the following values.

  1. The cube of x.
  2. The remainder when n is divided by 4.
  3. 1 if the product of x and y is less than 100, 0 otherwise.

Do your macros always work? If not, describe what arguments would make them fail.

  1. The Macro for finding the remainder when n is divided by 4 will not work (Invalid operands to binary expression 'int' and 'float'), when passing a float type argument (double too)
  2. The Macros won't work when passed an argument of type char * (string). This should have been obvious, but did so for the purpose of testing.

Apart from these errors, I think that the macros are well defined as taught in the chapter (the importance of extra parentheses)


Question 14.2

Write a macro NELEMS(a) that computes the number of elements in a one-dimensional array a. Hint: See the discussion of the sizeof operator in Section 8.1.

#include <stdio.h>

#define NELEMS(a) \
(sizeof (a) / sizeof (a[0]))

int main (void) {

int n;
// using size_t instead of int as the return type from using the sizeof operator is size_t

Question 14.3

Let DOUBLE be the following macro:

#define DOUBLE(x) 2*x

  1. What is the value of DOUBLE(1+2)?
  2. What is the value of 4/DOUBLE(2)?
  3. Fix the definition of DOUBLE.
  1. When the preprocessor encounters DOUBLE(1 + 2) invoked in the source file, it expands as the following:

    2 * 1 + 2 ---> 2 + 2 ---> 4

    The correct answer should have been:

    2 * (1 + 2) ---> 2 * 3 ---> 6
  2. When the preprocessor encounters 4 / DOUBLE(2) invoked in the source file, the macro is expanded as follows (along with other operands):

    4 / 2 * 2 ---> 2 * 2 ---> 4

    The correct answer should have been

    4 / (2 * 2) ---> 4 / 4 ---> 1
  3. Whenever we encounter the macro parameter in the replacement list, it is advised to use:

    • parentheses around the macro's parameter - in the replacement list - whenever it is used as an operand
    • parentheses around an operation, i.e. when an operator is present.

Question 14.4

For each of the following macros, give an example that illustrates a problem with the macro and show how to fix it.

  1. #define AVG(x,y) (x-y)/2
  2. #define AREA(x,y) (x)*(y)
  1. From initial inspection, the flaw I can find is that when we invoke AVG as AVG(15 - 6 + 3, 9 + 12 * 10), the following expansion would take place:
    NOTE: The operators +, -, *, and / are all left associative, so when we encounter an expression that has operator of similar precedence, the associativity comes into play

    -> (15 - 6 + 3 - 9 + 12 * 10) / 2 ---> (15 - 6 + 3 - 9 + 120) / 2 ---> (9 + 3 - 9 + 120) / 2 ---> (12 - 9 + 120) / 2 ---> (3 + 120) / 2 ---> 123 / 2 ---> 61

    The right answer should be:

    -> ((15 - 6 + 3) - (9 + 12 * 10)) / 2 ---> (12 - 129) / 2 ---> -117 / 2 ---> 58
  2. It seems like when we normally invoke AREA, there will be no error, but say that we define another macro called VOLUME that has the form:

    #define VOLUME(x, y, z) AREA(x, y) * z

    Now say that we invoke VOLUME by passing it the parameters as:

    -> VOLUME(12 + 3 * 6, 45 / 6 + 63, 13 * 43 / 12)
    -> AREA(12 + 3 * 6, 45 / 6 + 63) * 13 * 43 / 12)
    -> (12 + 3 * 6) * (45 / 6 + 43) * 13 * 43 / 12 ---> (30) * (50) * 13 * 3 ---> ...

    Another one that is described in the solution is the case when we invoke AREA as:

    1 / AREA(1, 2) ---> 1 / 1 * 2 ---> 1 * 2 ---> 2 (wrong)

Question 14.5

Let TOUPPER be the following macro:

#define TOUPPER(c) ('a' <= (c) && (c) <= 'z' ? (c) - 'a' + 'A' : (c))

Let s be a string and let i be an int variable. Show the output produced by each of the following program fragments.

  1. strcpy(s, "abcd");
    i = 0;
    putchar(TOUPPER(s[++i]));

  2. strcpy(s, "0123");
    i = 0;
    putchar(TOUPPER(s[++i]));

  1. Let's breakdown the problem, considering the first question:
    NOTE: Remember the fact that macros replaces the invocation by the replacement list, so it does not concern itself with values of variable, i.e. the place where macro is invoked is replaced as is.

    strcpy(s, "abcd") ---> s = "abcd\0"
    i = 0
    putchar(TOUPPER(s[++i])) ---> putchar(TOUPPER(s[++{0}])) ---> putchar(('a' <= (s[++{0}]) && (s[++{0}]) <= 'z' ? (s[++{0}]) - 'a' + 'A' : (s[++{0}])))

    NOTE: 0 indicates the value of i, which is initialized to be 0, and ++0 indicates the increment of the array subscript. ++0 only would not be easy to understand (also the fact that it may not always remiain 0 through out the statement as the operator used is pre-increment)

    How TOUPPER will work (not considering the case when the macro argument has side effect):

    TOUPPER('b') ---> ('a' <= 'b' && 'b' <= 'z') ---> 1 && 1 ---> 'b' - 'a' + 'A' ---> 98 - 97 + 65 ---> 66 ---> 'B'

    NOTE: This question has macro argument that produces the side effect. So the behavior is undefined.

  2. For the second question: NOTE: This does not consider the fact that the given question has a macro invocation that involves a side effect.

    strcpy(s, "0123") ---> s = "0123\0"
    i = 0
    putchar(TOUPPER(s[++i])) ---> putchar(TOUPPER(s[1]))
    TOUPPER('1') ---> ('a' <= '1' && '1' <= 'z') ---> 0 && 0 ---> '1'

    Considering the fact that the operator that produces side effect is used as a macro agrument, the result is undefined.


Question 14.6

(a) Write a macro DISP(f,x) that expands into a call of printf that displays the value of the function f when called with argument x. For example,

DISP(sqrt, 3.0);

should expand into

printf("sqrt(%g) = %g\n", 3.0, sqrt(3.0));

(b) Write a macro DISP2(f,x,y) that's similar to DISP but works for functions with two arguments.

#include <stdio.h>
#include <math.h>

#define DISP(f, x) \
printf(#f "(%g) = %g\n", x, f(x))

// undefining the isgreater macro which is defined in the math.h header, and defining my own
// #undef isgreater
// #define isgreater(x, y) \

Question 14.7

Let GENERIC_MAX be the following macro:

#define GENERIC_MAX(type)       \
type type##_max(type x, type y) \
{ \
return x > y ? x : y; \
}
  1. Show the preprocessor's expansion of GENERIC_MAX(long).
  2. Explain why GENERIC_MAX doesn't work for basic types such as unsigned long.
  3. Describe a technique that would allow us to use GENERIC_MAX with basic types such as unsigned long. Hint: Don't change the definition of GENERIC_MAX.
  1. When GENERIC_MAX(long) is called - with no semicolon at the end - and the following expansion will take place:

    long long_max(long x, long y) {
    return x > y ? x : y;
    }
  2. The reason why GENERIC_MAX doesn't work for basic types such as unsigned long, unsigned long long (C99), long double, and pretty much any data type that is made of more than one word, is, if we were to invoke GENERIC_MAX as GENERIC_MAX(unsigned long), the following expansion would happen:

    unsigned long unsigned long_max(unsigned long x, unsigned long y) {
    return x > y ? x : y;
    }

    NOTE: Notice that the function has an incorrect form - there is a spacing between unsigned and long_max - and so it is not understood as intented. The function definition should be of the form:

    return-type function-name(parameter_1, parameter_2, ...) {
    function body
    }
  3. To correct this, we can do what we did with boolean data types - introduce another macro that functions as we intend it to. For instance, if we need to use unsigned long as a data type for GENERIC_MAX, we need to do so as the following:

    #define ulint_t unsigned long

    GENERIC_MAX(ulint_t)

Question 14.8

Suppose we want a macro that expands into a string containing the current line number and file name. In other words, we'd like to write

const char *str = LINE_FILE;

and have it expand into

const char *str = "Line 10 of file foo.c";

where foo.c is the file containing the program and 10 is the line on which the invocation of LINE_FILE appears. Warning: This exercise is for experts only. Be sure to read the Q&A section carefully before attempting!

#include <stdio.h>

// see #71 of Chapter's notes
#define STR_LINE(x) #x
#define STR_LINE2(x) STR_LINE(x)
#define LINE_FILE "Line " STR_LINE2(__LINE__) " of file " __FILE__

int main (void) {

Question 14.9

Write the following parameterized macros.

  1. CHECK(x,y,n) - Has the value 1 if both x and y fall between 0 and n-1, inclusive.
  2. MEDIAN(x,y,z) - Finds the median of x, y, and z.
  3. POLYNOMIAL(x) - Computes the polynomial 3x5+2x45x3x2+7x63x^5 + 2x^4 - 5x^3 - x^2 + 7x - 6.
#include <stdio.h>

#define CHECK(x, y, n) \
(((x) >= 0 && (x) <= ((n) - 1)) ? ((y) >= 0 && (y) <= ((n) - 1)) ? 1 : 0 : 0)

#define MEDIAN(x, y, z) \
((x) <= (y) ? ((y) <= (z) ? y : (x) <= (z) ? z : x) : ((z) <= (y) ? y : (x) <= (z) ? x : z))

#define POLYNOMIAL(x) \

Question 14.10

Functions can often — but not always — be written as parameterized macros. Discuss what characteristics of a function would make it unsuitable as a macro.

Definition of a function that is defined as a macro has some limitations. Like we discussed in the Chapter, one of the reason is:

  1. A function that is defined through a macro cannot have a parameter that is of type pointer to the respective data type.

Some other limitations that I can think of is:

  1. A function can return not only basic types, but also of return type that is a pointer to the respective data type. Say, a function prototype is of the form: char *strcat (char *dst, const char *src);

    This function expects two parameters, dst - a pointer to character variable, and src - a pointer to const character variable. When the function reaches the end of it's function block, the return type is a pointer to character.

    Making a function like this using parameterized macros is not suitable as we cannot return a pointer when defining a function.

  2. A function that returns one type while having side effect on another variable - pointer to variables that are passed as an argument - is not possible to define using macros.

  3. A function that deals with modifying a string is not suitable to be defined using a macro, as string, in it's purest form is an array of characters that has a null character as it's terminating character.

  4. In short, any function that involves the side effect on its argument, provided that the argument is a pointer to a certain data type, is not feasible to be defined using a macro.


Question 14.11

(C99) C programmers often use the fprintf function to write error messages:

fprintf(stderr, "Range error: index = %d\n", index);

stderr is C's "standard error" stream; the remaining arguments are the same as those for printf, starting with the format string. Write a macro named ERROR that generates the call of fprintf shown above when given a format string and the items to be displayed:

ERROR("Range error: index = %d\n", index);

#include <stdio.h>

#define ARR_LEN 10

#define ERROR(msg, value) \
fprintf(stderr, #msg, value)

int main (void) {

Question 14.12

Suppose that the macro M has been defined as follows:

#define M 10

Which of the following tests will fail?

  1. #if M
  2. #ifdef M
  3. #ifndef M
  4. #if defined(M)
  5. #if !defined(M)

Since M is a macro that is defined and the replacement list contains 10, whenever M is appeared in the file, M is replaced by 10

So, the following ones will be passed and failed:

  1. Passed, as M is not only defined, but also has a nonzero value
  2. Passed, as M is defined (it does not matter if M has a zero or nonzero value)
  3. Failed, as M is defined, but #ifndef will pass only if M is not defined
  4. Passed, this is the same as using #ifdef - which is similar to question b - and it passes the test
  5. Failed, as !defined(M) will pass only if M is not defined.

Question 14.13

  1. Show what the following program will look like after preprocessing. You may ignore any lines added to the program as a result of including the <stdio.h> header.

    #include <stdio.h>

    #define N 100

    void f(void);

    int main(void)
    {
    f();
    #ifdef N
    #undef N
    #endif
    return 0;
    }

    void f(void)
    {
    #if defined(N)
    printf("N is %d\n", N);
    #else
    printf("N is undefined\n");
    #endif
    }
  2. What will be the output of this program?

  1. The program after the preprocessing will be (in its simplest form - remove stdio library and its functions and add a random variable initialization inside the function call):

    # 1 "exercise_13.c"
    # 1 "<built-in>" 1
    # 1 "<built-in>" 3
    # 400 "<built-in>" 3
    # 1 "<command line>" 1
    # 1 "<built-in>" 2
    # 1 "exercise_13.c" 2
    # 41 "exercise_13.c"
    void f(void);

    int main(void)
    {
    f();




    return 0;
    }

    void f(void)
    {





    int x = 10;

    }

    The actual question's output:

          stdio's content
    ...

    extern int __vsnprintf_chk (char * restrict, size_t, int, size_t,
    const char * restrict, va_list);
    # 417 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/stdio.h" 2 3 4
    # 68 "exercise_13.c" 2



    void f(void);

    int main(void)
    {
    f();




    return 0;
    }

    void f(void)
    {




    printf("N is undefined\n");


    }
  2. The output of the program will be:
    -> N is undefined


Question 14.14

Show what the following program will look like after preprocessing. Some lines of the program may cause compilation errors; find all such errors.

#define N = 10
#define INC(x) x+1
#define SUB (x,y) x-y
#define SQR(x) ((x)*(x))
#define CUBE(x) (SQR(x)*(x))
#define M1(x,y) x##y
#define M2(x,y) #x #y

int main(void)
{
int a[N], i, j, k, m;
#ifdef N
i = j;
#else
j = i;
#endif
i = 10 * INC(j);
i = SUB(j, k);
i = SQR(SQR(j));
i = CUBE(j);
i = M1(j, k);
puts(M2(i, j));

#undef SQR
i = SQR(j);
#define SQR
i = SQR(j);

return 0;
}

The output of the program may be (after we fix some errors in #define directive and executing the program without any initialization for variables used in calculation): -> ij

The preprocessor's output for the faulty code:

# 1 "exercise_14.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 400 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "exercise_14.c" 2
# 50 "exercise_14.c"
int main(void)
{
int a[= 100], i, j, k, m;




i = j;




i = 10 * j+1;
i = j-k;
i = ((((j)*(j)))*(((j)*(j))));
i = (((j)*(j))*(j));
i = jk;
puts("i" "j");


i = SQR(j);

i = (j);

return 0;
}

Question 14.15

Suppose that a program needs to display messages in either English, French, or Spanish. Using conditional compilation, write a program fragment that displays one of the following three messages, depending on whether or not the specified macro is defined:

Insert Disk 1
Inserez Le Disque 1
Inserte El Disco 1
(if ENGLISH is defined)
(if FRENCH is defined)
(if SPANISH is defined)
#include <stdio.h>

// #define ENGLISH
#define FRENCH
#define SPANISH

#define ERR_MSG(msg) \
fprintf(stderr, "ERROR: " #msg " not defined\n")

Question 14.16

(C99) Assume that the following macro definitions are in effect:

#define IDENT(x) PRAGMA(ident #x)
#define PRAGMA(x) _Pragma(#x)

What will the following line look like after macro expansion?

IDENT(foo)

The expansion will look like this (see #66 of Chapter's notes):

IDENT(foo) ---> PRAGMA(ident #foo) ---> PRAGMA(ident "foo") ---> _Pragma("ident "foo"") ---> _Pragma("ident \"foo\"") ---> #pragma ident "foo"