The Preprocessor
Exercises
Question 14.1
Write parameterized macros that compute the following values.
- The cube of
x. - The remainder when
nis divided by 4. - 1 if the product of
xandyis less than 100, 0 otherwise.
Do your macros always work? If not, describe what arguments would make them fail.
- Answer
- Output
- Program
- The Macro for finding the remainder when
nis divided by 4 will not work (Invalid operands to binary expression'int'and'float'), when passing afloattype argument (doubletoo) - 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)
The current value of i and j are: 10 and 20 [INT TEST CUBE] The result obtained from calling the CUBE(i) macro is: 1000 [INT TEST CUBE] The result obtained from calling the CUBE(i + 10) macro is: 8000 [INT TEST REM_WHEN_DIV_BY_FOUR] The result obtained from calling REM_WHEN_DIV_BY_FOUR(j) is: 0 [INT TEST REM_WHEN_DIV_BY_FOUR] The result obtained from calling REM_WHEN_DIV_BY_FOUR(j / 100) is: 0 [INT TEST CHECK_LESS_THAN_100] The result obtainted from calling CHECK_LESS_THAN_100(i, j) is: 0 [INT TEST CHECK_LESS_THAN_100] The result obtainted from calling CHECK_LESS_THAN_100(i + 100, j - 100) is: 1
#include <stdio.h>
#define CUBE(x) ((x) * (x) * (x))
#define REM_WHEN_DIV_BY_FOUR(x) \
((x) % 4)
#define CHECK_LESS_THAN_100(x, y) \
(((x) * (y)) < 100 ? 1 : 0)
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.
- Program
- Output
#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
Declare an array of size: 5 [NELEMS OUTPUT] The length of the array as given by the NELEMS macro is: 5
Question 14.3
Let DOUBLE be the following macro:
#define DOUBLE(x) 2*x
- What is the value of
DOUBLE(1+2)? - What is the value of
4/DOUBLE(2)? - Fix the definition of
DOUBLE.
- Answer
- Output
- Program
-
When the preprocessor encounters
DOUBLE(1 + 2)invoked in the source file, it expands as the following:2 * 1 + 2 ---> 2 + 2 ---> 4The correct answer should have been:
2 * (1 + 2) ---> 2 * 3 ---> 6 -
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 ---> 4The correct answer should have been
4 / (2 * 2) ---> 4 / 4 ---> 1 -
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.
[a] After invoking
DOUBLE(1 + 2), the result obtained is: 4.000000 [b] After invoking4 / DOUBLE(2), the result obtained is: 4.000000
#include <stdio.h>
#define DOUBLE(x) 2 * x
#define DOUBLE_FIXED(x) (2 * (x))
int main (void) {
double result = DOUBLE(1 + 2);
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.
#define AVG(x,y) (x-y)/2#define AREA(x,y) (x)*(y)
- Answer
- Output
- Program
-
From initial inspection, the flaw I can find is that when we invoke
AVGasAVG(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 ---> 61The right answer should be:
-> ((15 - 6 + 3) - (9 + 12 * 10)) / 2 ---> (12 - 129) / 2 ---> -117 / 2 ---> 58 -
It seems like when we normally invoke
AREA, there will be no error, but say that we define another macro calledVOLUMEthat has the form:#define VOLUME(x, y, z) AREA(x, y) * zNow 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)
[AVG TEST] The result of the macro invocation AVG(15 - 6 + 3, 9 + 12 * 10) is: 61 [AVG_FIXED TEST] The result of the macro invocation AVG_FIXED(15 - 6 + 3, 9 + 12 * 10) is: -58 [AREA TEST] The result of the macro invocation 1 / AREA(1, 2) is: 2 [AREA_FIXED TEST] The result of the macro invocation 1 / AREA_FIXED(1, 2) is: 0 [VOLUME TEST] The result of the macro invocation VOLUME(12 + 3 * 6, 45 / 6 + 63, 13 * 43 / 12) is: 97825 [VOLUME_FIXED TEST] The result of the macro invocation VOLUME_FIXED(12 + 3 * 6, 45 / 6 + 63, 13 * 43 / 12) is: 96600
#include <stdio.h>
#define AVG(x, y) (x - y) / 2
#define AVG_FIXED(x, y) (((x) - (y)) / 2)
#define AREA(x, y) (x) * (y)
#define AREA_FIXED(x, y) ((x) * (y))
#define VOLUME(x, y, z) AREA(x, y) * z
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.
-
strcpy(s, "abcd");
i = 0;
putchar(TOUPPER(s[++i])); -
strcpy(s, "0123");
i = 0;
putchar(TOUPPER(s[++i]));
- Answer
- Output
- Program
-
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
TOUPPERwill 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.
-
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.
D2
#include <stdio.h>
#include <string.h>
#define STR_LEN 100
#define TOUPPER(c) ('a' <= (c) && (c) <= 'z' ? (c) - 'a' + 'A' : (c))
int main (void) {
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.
- Program
- Output
#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) \
sqrt(25) = 5 Calling fmodl, the division of 12.500000 by 4.500000 has the remainder: 3.500000
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; \
}
- Show the preprocessor's expansion of
GENERIC_MAX(long). - Explain why
GENERIC_MAXdoesn't work for basic types such asunsigned long. - Describe a technique that would allow us to use
GENERIC_MAXwith basic types such asunsigned long. Hint: Don't change the definition ofGENERIC_MAX.
- Answer
- Output
- Program
-
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;
} -
The reason why
GENERIC_MAXdoesn't work for basic types such asunsigned 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 invokeGENERIC_MAXasGENERIC_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
unsignedandlong_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
} -
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 longas a data type forGENERIC_MAX, we need to do so as the following:#define ulint_t unsigned long
GENERIC_MAX(ulint_t)
Calling GENERIC_MAX with the type of unsigned long is: 120 Calling GENERIC_MAX with the type of long is: 500
#include <stdio.h>
#define ulint_t unsigned long
#define GENERIC_MAX(type) \
type type##_max (type x, type y) { \
return x > y ? x : y; \
}
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!
- Program
- Output
#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) {
Line 10 of file /tmp/WPj23WAlFd/main.c
Question 14.9
Write the following parameterized macros.
CHECK(x,y,n)- Has the value 1 if both x and y fall between 0 and n-1, inclusive.MEDIAN(x,y,z)- Finds the median ofx,y, andz.POLYNOMIAL(x)- Computes the polynomial .
- Program
- Output
#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) \
The result obtained from invoking CHECK is: 1 The result obtained from invoking MEDIAN is: 10 The result obtained from invoking POLYNOMIAL is: 10004
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.
- Answer
- Program
Definition of a function that is defined as a macro has some limitations. Like we discussed in the Chapter, one of the reason is:
- 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:
-
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.
-
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.
-
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.
-
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.
// #include <stdio.h>
// this macro is used to increment all elements in an array
// obviously this will not work, as the indirection operation is not fully utilized when creating a macro
// the error that is generated when invoking the macro is shown below when the macro is invoked.
// #define GENERIC_INC_ARR_ELEMENTS(type) \
// type *type##_inc_arr (type *a) { \
// int size = sizeof(*a) / sizeof(*(a + 0)); \
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);
- Program
- Output
#include <stdio.h>
#define ARR_LEN 10
#define ERROR(msg, value) \
fprintf(stderr, #msg, value)
int main (void) {
Enter the index (0-9) and the value that needs to be added to that index: 3 12 Element 0 has the value: 0 Element 1 has the value: 0 Element 2 has the value: 0 Element 3 has the value: 12 Element 4 has the value: 0 Element 5 has the value: 0 Element 6 has the value: 0 Element 7 has the value: 0 Element 8 has the value: 0 Element 9 has the value: 0
Question 14.12
Suppose that the macro M has been defined as follows:
#define M 10
Which of the following tests will fail?
#if M#ifdef M#ifndef M#if defined(M)#if !defined(M)
- Answer
- Output
- Program
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:
- Passed, as
Mis not only defined, but also has a nonzero value - Passed, as
Mis defined (it does not matter ifMhas a zero or nonzero value) - Failed, as
Mis defined, but#ifndefwill pass only ifMis not defined - Passed, this is the same as using
#ifdef- which is similar to question b - and it passes the test - Failed, as
!defined(M)will pass only ifMis not defined.
#if M has passed. #ifdef M has passed. #ifndef M has failed. #if defined(M) has passed. #if !defined(M) has failed.
#include <stdio.h>
#define M 10
#if M
#define IF_PASSED 1
#endif
#ifdef M
Question 14.13
-
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
} -
What will be the output of this program?
- Answer
- Output
- Program
-
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");
} -
The output of the program will be:
-> N is undefined
N is undefined
#include <stdio.h>
#define N 100
void f(void);
int main(void)
{
f();
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;
}
- Answer
- Output
- Program
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;
}
ij
#include <stdio.h>
#define N 100
#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
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 1Inserez Le Disque 1Inserte El Disco 1 | (if ENGLISH is defined)(if FRENCH is defined)(if SPANISH is defined) |
- Program
- Output
#include <stdio.h>
// #define ENGLISH
#define FRENCH
#define SPANISH
#define ERR_MSG(msg) \
fprintf(stderr, "ERROR: " #msg " not defined\n")
Select Language (displays all by default):
- English
- French
- Spanish Choose your option: 2 Inserez Le Disque 1
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)
- Answer
- Output
- Program
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"
#include <stdio.h>
#define IDENT(x) PRAGMA(ident #x)
#define PRAGMA(x) _Pragma(#x)
int main (void) {
IDENT(foo)