CSC161 2010F Imperative Problem Solving
[Skip to Body]
Primary:
[Front Door]
[Schedule]
-
[Academic Honesty]
[Instructions]
Current:
[Outline]
[EBoard]
-
[Assignment]
[Lab]
Groupings:
[EBoards]
[Assignments]
[Examples]
[Exams]
[Handouts]
[Labs]
[Outlines]
[Readings]
Related Courses:
[CSC195 2003S (Rebelsky)]
[CSC161 2009F (Coahran)]
[CSC161 2010S (Walker)]
Misc:
[SamR]
[ISO]
[GNU Coding Standards]
Summary:
Prerequisites: Familiarity with basic C. Familiarity with Linux.
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
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
#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.
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.
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.
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
.
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.
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
.
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.
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.
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?
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.
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?
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?
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.
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.
__HEADER_H__
(read as underscore, underscore, header name, underscore, capital H, underscore, underscore).
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.)
Thursday, 14 October 2010 [Samuel A. Rebelsky]
Friday, 15 October 2010 [Samuel A. Rebelsky]
[Skip to Body]
Primary:
[Front Door]
[Schedule]
-
[Academic Honesty]
[Instructions]
Current:
[Outline]
[EBoard]
-
[Assignment]
[Lab]
Groupings:
[EBoards]
[Assignments]
[Examples]
[Exams]
[Handouts]
[Labs]
[Outlines]
[Readings]
Related Courses:
[CSC195 2003S (Rebelsky)]
[CSC161 2009F (Coahran)]
[CSC161 2010S (Walker)]
Misc:
[SamR]
[ISO]
[GNU Coding Standards]
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