Mini-unit 1: preprocessor
Learning goals
You will learn the following concepts/skills:
- C preprocessor − how to use several of the most commonly used directives
- #define symbols – not just for defining constants
- #define macros – like functions but with special capabilities
#ifdef
/#ifndef
– disable sections of code with a#define
or GCC flag#include
guards – enable more versatile use of header (.h) files.
- ANSI color codes – Print text in color on the terminal
Overview of the Miniunit series (HW06, HW07, HW08)
This assignment is part 1 of a 3-part series collectively referred to as miniunit.
Real-world software projects typically comprise tens, hundreds, or many thousands of files. Even in your ECE courses, your programming assignments will be getting bigger and more complex. Testing code by hand (i.e., by playing with it) is useless since there are so many components, and ways things can go wrong. Hand-testing effectively would be complex—and prohibitively slow.
Most serious software projects use two tools: a unit testing framework
(e.g., CUnit)
to organize and coordinate tests, and a build system
(e.g., make)
to manage the process of compiling, testing, and deploying the project. In this homework,
you will get a light introduction to both. In addition, you will learn to use console
logging effectively (as a complement to gdb), without littering your code with
printf(…)
statements.
In the Miniunit Series, you will create the following:
-
clog.h – your own library for smarter
printf
-style debugging and logging. From now on, instead of debugging withprintf(…)
directly, you will use function-like macros to print values in various formats and colors to make the output easier to view. You will use preprocessor directives to ensure that your debugging code does not interfere with your tests, or show up inadvertently when others are testing your code. - Makefile – input file for the make build system. You will be able to build, test, submit, and/or pre-test your code with one command from bash or directly from Vim (and other editors).
-
miniunit.h – your own simple unit test library. You
can use this to test future assignments in this class, or for any other project you do
in C (or C++) beyond ECE 264. This consists of four
#define
macros that you can use in your test code. The most important macro you will create ismu_check(…)
, which is somewhat similar toassert(…)
.
Starter code
You will create your clog.h from scratch (i.e., starting with a blank file).
There is also a script called print256colors.sh (created by Tom Hale). It will not be used directly for the assignment, but running this script will give you some exposure to the range of colors your terminal can display.
Run 264get hw06
to
fetch these files.
For HW06: clog.h
For this part, you will create a reusable library for debugging any C project. In the process, you will learn about the C preprocessor and ANSI control codes.
Instructions
Get the starter code.
you@ecegrid-thin1 ~
$
cd 264
you@ecegrid-thin1 ~/264
$
264get hw06
you@ecegrid-thin1 ~/264
$
cd hw06
you@ecegrid-thin1 ~/264/hw06
$
Create test_clog.c with a test for debugf(…)
Create a test file called test_clog.c.
you@ecegrid-thin1 ~/264/hw06
$
vim test_clog.c
The debugf(…)
function-like macro will behave similarly to printf(…)
, except that debugf(…)
will prepend the output with the filename and line number.
Add a test for the
debugf(…)
function-like macro as follows. Please hand-copy this code.
#include <stdlib.h>
#include "clog.h"
int main(int argc, char* argv[]) {
debugf("%d%% of Columbia live in %s.\n", 15, "Bogota");
return EXIT_SUCCESS;
}
Create clog.h
you@ecegrid-thin1 ~/264/hw06
$
vim clog.h
Add an include guard to your clog.h.
Tip: In Vim, from insert mode, you can type once
and press
tab to create the include guard for you automatically. (This uses the Snipmate plugin, which
we have installed for you on ecegrid.)
Implement debugf(…)
Add the implementation for debugf(…)
to your clog.h.
It should do two things:
- Print the filename and line number where
debugf(…)
was called in the format[{filename}:{linenumber}]
. For example,[test_clog.c:5]
. Note the trailing space. - Pass all arguments to
printf(…)
.
This will require a multi-line macro.
Hint #1: The __FILE__
and __LINE__
macros will print the filename and line number. You will also need to use __VA_ARGS__
to pass the arguments to printf(…)
. You should call printf(…)
at least twice.
Hint #2: See Q&A #4 for information on making a multiline macro.
Test debugf(…)
Compile and run your test_clog.c. You should get output similar to the following. Note the line number printed in the example output may differ compared to the output you get. The important thing is it matches the line number showed by Vim in your test_clog.c. It would be worthwhile to test debugf(…)
on multiple lines.
you@ecegrid-thin1 ~/264/hw06
$
gcc test_clog.c -o test_clog
you@ecegrid-thin1 ~/264/hw06
$
./test_clog
you@ecegrid-thin1 ~/hw06
$
Add a test for printf_red(…)
The printf_red(…)
function-like macro will work just like
printf(…)
, except that text will be printed in red to the terminal.
Add a test for the printf_red(…)
function-like macro to your
test_clog.c, at the end of main(…)
(i.e., just before the
return EXIT_SUCCESS;
). Please hand-copy this code verbatim.
printf_red("%s\n", "RED");
Implement printf_red(…)
Add the implementation for printf_red(…)
to your clog.h.
It should do three things:
- Print the ANSI code for red (
\x1b[31m
) to tell the terminal to print the subsequent text in red. - Pass all arguments to
printf(…)
. - Print the ANSI code for reset (
\x1b[0m
) to tell the terminal to stop printing text in red.
This will require a multi-line macro.
Test everything up to printf_red(…)
(…).
Compile and run your test_clog.c. You should get the following output. The word “RED” should be displayed in red print.
you@ecegrid-thin1 ~/hw06
$
gcc test_clog.c -o test_clog
you@ecegrid-thin1 ~/hw06
$
./test_clog
you@ecegrid-thin1 ~/hw06
$
Add tests for the other printf_«color»(…) function-like macros to test_clog.c.
Add test for the
printf_green(…)
,
printf_yellow(…)
,
printf_blue(…)
,
printf_magenta(…)
, and
printf_cyan(…)
function-like macros to your
test_clog.c, at the end of main(…)
(i.e., just after the test
for red and before the
return EXIT_SUCCESS;
). Each should print the color name in uppercase in that color.
Implement the other printf_«color»(…) function-like macros.
Add function-like macros for
printf_green(…)
,
printf_yellow(…)
,
printf_blue(…)
,
printf_magenta(…)
, and
printf_cyan(…)
to your
clog.h
Search the web to find the remaining ANSI codes. Note that in standard C, the escape character is
\x1b
(not \e
).
Test everything up to the printf_«color»(…)
function-like macros.
you@ecegrid-thin1 ~/hw06
$
gcc test_clog.c -o test_clog
you@ecegrid-thin1 ~/hw06
$
./test_clog
you@ecegrid-thin1 ~/hw06
$
Add tests for log_int(…)
The log_int(…)
function-like macro will print
an integer value, along with the text of argument
to log_int(…)
.
Example: log_int(3 + 4)
should print 3 + 4 == 7 .
This is something you can do with a macro, but not a function.
Add the following tests for log_int(…)
. Please hand-copy this code.
log_int(3 + 4);
int result = 15;
log_int(result);
Implement log_int(…)
.
Add a function-like macro for log_int(…)
in your
clog.h.
Test everything up to log_int(…)
.
you@ecegrid-thin1 ~/hw06
$
gcc test_clog.c -o test_clog
you@ecegrid-thin1 ~/hw06
$
./test_clog
you@ecegrid-thin1 ~/hw06
$
Add tests for log_char(…)
log_char(…)
will print a character, wrapped in single quotation marks,
along with the text of the argument to log_char(…)
.
Ex: log_char('A')
should print 'A' == 'A'.
Ex: log_char(65)
should print 65 == 'A'.
Add the following tests for log_char(…)
. Please hand-copy this code.
log_char('A');
log_char(65);
char* class_name = "ECE 264";
log_char(class_name[0]);
Implement log_char(…)
.
Add a function-like macro for log_char(…)
in your clog.h.
Test everything up to log_char(…)
.
you@ecegrid-thin1 ~/hw06
$
gcc test_clog.c -o test_clog
you@ecegrid-thin1 ~/hw06
$
./test_clog
you@ecegrid-thin1 ~/hw06
$
Add tests for log_str(…)
log_str(…)
will print a string, wrapped in double quotation marks,
along with the text of the argument to log_str(…)
.
Ex: log_str(class_name)
should print class_name == "ECE 264" .
Add the following tests for log_str(…)
. Please hand-copy this code.
log_str(class_name);
Implement log_str(…)
.
Add a function-like macro for log_str(…)
in your clog.h.
To include double quotation marks in a string, precede with a backslash. For example,
printf("Say \"Hi\"")
prints
Say "Hi" .
Test everything up to log_str(…)
..
you@ecegrid-thin1 ~/hw06
$
gcc test_clog.c -o test_clog
you@ecegrid-thin1 ~/hw06
$
./test_clog
you@ecegrid-thin1 ~/hw06
$
Add tests for log_addr(…)
log_addr(…)
will print a memory address in hexadecimal notation,
along with the text of the argument to log_addr(…)
.
Recall that when we declare a string on the data segment (e.g., using
char* s = "▒▒▒";
), the variable (e.g., s
) is actually the address
in memory of the first character in that string.
Ex: log_addr(class_name)
should print class_name == 0x400990 or similar. The exact memory address may be different.
Add the following tests for log_addr(…)
. Please hand-copy this code.
log_addr(class_name);
Implement log_addr(…)
.
Add a function-like macro for log_addr(…)
in your clog.h.
To print a memory address using hexadecimal notation, use the %p
format code to printf(…)
and typecast the address argument to
void*
. Example: char* s = "abc"; printf("%p", (void*)s)
.
Test everything up to log_addr(…)
..
you@ecegrid-thin1 ~/hw06
$
gcc test_clog.c -o test_clog
you@ecegrid-thin1 ~/hw06
$
./test_clog
you@ecegrid-thin1 ~/hw06
$
Add tests for log_float(…)
log_float(…)
will print a floating point number
(float
or double
) with 16 digits to the right of the decimal point.
Ex: log_float(0.5 / 2.0)
should print 0.5 / 2.0 == 0.2500000000000000 .
Add the following tests for log_addr(…)
. Please hand-copy this code.
log_float(1.0 / 8.0);
Implement log_float(…)
.
To instruct printf(…)
to print a floating point number with
16 digits to the right of the decimal point, use the format code
%.016f
.
Test everything up to log_float(…)
..
you@ecegrid-thin1 ~/hw06
$
gcc test_clog.c -o test_clog
you@ecegrid-thin1 ~/hw06
$
./test_clog
you@ecegrid-thin1 ~/hw06
$
Add tests for log_bool(…)
log_bool(…)
will print false
if the condition is false, or
true
otherwise, along with the text of the argument to log_bool(…)
.
Ex: log_bool(3 > 5)
should print 3 > 5 == false .
Ex: log_bool(3 > 1)
should print 3 > 1 == true .
Add the following tests for log_addr(…)
. Please hand-copy this code.
log_bool(3 > 5);
log_bool(3 > 1);
Implement log_bool(…)
.
Tip: You can simplify (and shorten) your code using the ternary operator (▒?▒:▒
).
Test everything up to log_bool(…)
..
Your clog.h should be finished now. Let's test a little more thoroughly.
Start with the normal test.
you@ecegrid-thin1 ~/hw06
$
gcc test_clog.c -o test_clog
you@ecegrid-thin1 ~/hw06
$
./test_clog
you@ecegrid-thin1 ~/hw06
$
Modify test_count_words.c to use log_int(…)
We are providing a simple program for counting words, along with a rudimentary test program.
Modify test_count_words.c to use log_int(…)
instead of
printf(…)
.
How much work is this?
Requirements
- Your submission must contain each of the following files, as specified:
file contents clog.h macros debugf(▒▒▒)
-
Equivalent to
printf(▒▒▒)
except text is prepended by the line and filenumber where the macro is invoked in the following format:[{filename}:{linenumber}] {printf_output}
. -
Example:
debugf("Hello")
in test_clog.c at line 5 will print[test_clog.c:5] Hello
printf red(▒▒▒)
-
Equivalent to
printf(▒▒▒)
except text is printed in red. - ⚠The specification for this macro above is not code. For your actual
#define
you will need something likeprintf_red(...)
. In the RHS, use__VA_ARGS__
. Search the web for “variadic macros” for more information on this.
printf green(format, ...)
- Like
printf_red(…)
but in green - See the warning in
printf_red(…)
.
printf yellow(format, ...)
- Like
printf_red(…)
but in yellow - See the warning in
printf_red(…)
.
printf blue(format, ...)
- Like
printf_red(…)
but in blue - See the warning in
printf_red(…)
.
printf magenta(format, ...)
- Like
printf_red(…)
but in magenta - See the warning in
printf_red(…)
.
printf cyan(format, ...)
- Like
printf_red(…)
but in cyan - See the warning in
printf_red(…)
.
log int(n)
- Calling
log_int(3+3)
should print this:3+3 == 6
-
Here's a naïve way to do it:
#define log_int(n) printf("%s == %d\n", (#n), (n))
You may copy/adapt that.
log str(s)
-
log_str(s)
⇔printf("s == \"%s\"\n", s)
char* s = "abc"; log_str(s);
should prints == "abc"
log_str("xyx")
should print"xyx" == "xyx"
log float(n)
-
log_float(n)
⇔printf("n == '%.016f'\n", n)
log char(ch)
-
log_char(ch)
⇔printf("ch == '%c'\n", ch)
log bool(condition)
-
log_bool(condition)
prints condition == false if condition is false, or condition == true otherwise.
log addr(addr)
-
log_addr(addr)
⇔printf("addr == %p\n", addr)
- Examples:
int n = 5; log_addr(&n)
// should print something like&n == 0x7fff938d
int* a_n = &n; log_addr(a_n)
// should print something likea_n == 0x7fff938d
- Note: When passing an address to
printf("… %p …")
, you must typecast it tovoid*
.
test_clog.c test code main(▒▒▒)
- Follow the instructions to create this file.
test_count_words.c test code main(▒▒▒)
- Modify the test_count_words.c starter file to use
log_int(…)
instead ofprintf(…)
-
Equivalent to
- You may hand-copy any code snippets you find in this homework
description into your HW06 submission.
- Do not use copy-paste. You learn more from hand-copying unfamiliar syntax. Expect problems if you ignore this.
- Adaptation is strongly recommended. Some snippets may not work in your file as is.
- Be sure you understand what you are copying. Correct functioning of your code is your responsibility.
- Copying from this page is not necessary. This permission is given as a convenience, since some of the syntax may be unfamiliar, and this homework is more tightly specified than most others.
- Names of helper macros (if any) must begin with “
__mu_
”. -
You may use any of the following:
header functions/symbols allowed in… stdbool.h bool
,true
,false
*.c
,*.h
stdio.h printf
,fputs
,fprintf
,stdout
*.c
,*.h
string.h strcmp
test_count_words.c
,miniunit.h
stdlib.h EXIT_SUCCESS
test_count_words.c
,test_clog.c
unistd.h isatty
,STDOUT_FILENO
*.c
,*.h
-
Submissions must meet the code quality standards
and the course policies on homework and academic integrity.
-
That means everything must compile successfully, even when compiled
with the usual compiler flags
(
gcc -g -std=c11 -Wall -Wshadow -Wvla -Werror -pedantic
)—and both with and without-D DBG_LOG
. Furthermore, tests using your miniunit.h should work properly with or without-D DBG_LOG
.
-
That means everything must compile successfully, even when compiled
with the usual compiler flags
(
- Write multi-line macros with one C statement on each line. Do not try to cram many statements on a single line of code. That would not be readable.
- Indent your macros similarly to regular C code. Your code must be readable.
Submit
To submit HW06 from within your hw06 directory,
type
264submit HW06 clog.h test_clog.c test_count_words.c
Pretester
Q&A
How do I use a variadic macro to pass arguments from
In the LHS of the #define, use “printf_red(…)
toprintf(…)
...
” to indicate 1 or more arguments. In the RHS, use __VA_ARGS__ to pass those same arguments to the next function (e.g.,printf(…)
). Note that “...
” stands for one or more, not zero or more.Here's a simple example:
#define my_printf(...) printf(__VA_ARGS__)
That macro would convertmy_printf("%d", 3)
toprintf("%d", 3)
.Should I have a semicolon at the end of the RHS of a
No. The person using the macro will normally include the semicolon.#define
macroHow can I continue the RHS of a
Put a backslash ("\") at the end of the line.#define
macro definition onto the next line#define profess_love_for_food(food) printf("I love %s", \ food)
Can I have a macro with multiple C statements
Yes. In theory, you could just have the two statements separated by a semicolon like this:// BAD #define profess_love_for_two_foods(food1, food2) \ printf("I love %s", food1); \ printf("I love %s", food2)
However, that would lead to surprising results if someone who doesn't follow the code quality standards calls that macro in an if statement like this:if(1 == 0) profess_love_for_two_foods("soap", "poison");
Only the first statment would be connected to theif
statement.if(1 == 0) printf("I love %s", "soap"); printf("I love %s", "poison");
It is tempting to just put curly braces around the two statements, but that also causes problems.// BAD #define profess_love_for_two_foods(food1, food2) { \ printf("I love %s", food1); \ printf("I love %s", food2); \ }
The problem comes back to uncivilized oafs who write
if
statements without curly braces, like this:if(age >= 30) profess_love_for_two_foods("chocolate", "pizza"); else profess_love_for_two_foods("spinach", "broccoli");
The above example would result in this:
if(age >= 30) { printf("I love %s", "chocolate"); printf("I love %s", "pizza"); }; ← PROBLEM else { printf("I love %s", "spinach"); printf("I love %s", "broccoli"); };
The standard solution is to wrap the statements in ado { … } while(false)
block. Because thedo…while
requires a semicolon, this actually works out like we want.Yes, it is ugly. Hacks like this are not something the instructor would normally condone, but it is standard practice because there are very few truly versatile options for this.// USE THIS WAY #define profess_love_for_two_foods(food1, food2) do { \ printf("I love %s", food1); \ printf("I love %s", food2); \ } while(false)
How do I convert the
printf(…)
statements in test_count_words.c to uselog_int(…)
Here is an unrelated example that prints the output of a function usingprintf(…)
.#include <stdio.h> #include <stdlib.h> int triple(int n) { return n * 3; } int main(int argc, char* argv[]) { // BAD (… or less good) printf("triple(5) == %d\n", triple(5)); printf("triple(4) == %d\n", triple(4)); printf("triple(3) == %d\n", triple(3)); printf("triple(2) == %d\n", triple(2)); printf("triple(1) == %d\n", triple(1)); return EXIT_SUCCESS; }
Here is the same example converted to uselog_int(…)
.#include <stdio.h> #include <stdlib.h> #include "clog.h" int triple(int n) { return n * 3; } int main(int argc, char* argv[]) { // GOOD log_int(triple(5)); log_int(triple(4)); log_int(triple(3)); log_int(triple(2)); log_int(triple(1)); // Advantage over raw printf(…): Less duplication means fewer oppportunities for bugs. return EXIT_SUCCESS; }
Withlog_int(…)
, you can print the expression itself, along with its value.What does
It expands to the text of the expression, instead of its value. This is easiest to see if you test using the 264cpp (or /usr/bin/cpp) command.#x
do in a#define
macroHere is an example, which uses thelog_int(…)
snippet given in the Requirements table.// demonstrate_hash.c #include <stdio.h> #include <stdlib.h> #define log_int(n) printf("%s == %d\n", (#n), (n)) int main(int argc, char* argv[]) { log_int(3 + 3); return EXIT_SUCCESS; }
If we process that with the preprocessor directly (instead of via gcc), we can see what it becomes. Note:264cpp
is just a shortcut for/usr/bin/cpp ▒▒▒ | indent -kr
that also cleans up the output a bit to make it easier to read.you@ecegrid-thin1 ~/HW06 $
/usr/bin/cpp demonstrate_hash.c | indent -kr
… int main(int argc, char* argv[]) { printf("%s == %d\n", ("3 + 3"), (3 + 3)); return 0; }Notice that the second argument toprintf(…)
is a string literal,"3 + 3"
—the text of the argument that was passed tolog_int(…)
. That is different from the third argument, which is the value of that parameter,3 + 3
(= 6).GCC: “ISO C99 requires rest arguments to be used.” ⋯??
In variadic macros (see Q1), the...
can stand for 1 or more arguments. If yourprintf_red(…)
looks like#define printf_red(format, ...) ▒▒▒▒▒▒▒▒▒▒
, it will likely work as long as you pass ≥2 arguments (e.g.,printf_red("I like %d", 5)
), but if you pass only a string (e.g.,printf_red("orange sky")
), then there are 0 arguments for the...
. It needs ≥1. The solution for our purposes is to remove the first argument (format
).GCC: “error: ‘true’ undeclared” ⋯???
The
GCC: “error: ‘false’ undeclared” ⋯??true
andfalse
constants are defined in a standard header called stdbool.h; you need to include it (i.e.,#include <stdbool.h>
) in any file where you use them (e.g., clog.h). This was communicated in #8 of the Requirements table, as well as the Code Quality Standards, which have more aboutbool
,true
, andfalse
.CPP: "Unexpected EOF (end-of-file)" (or similar). Why?
You probably have unmatched braces somewhere in your clog.h or miniunit.h. First, fix your indents. Many errors will become much easier to spot. Then, be sure to use264cpp test_count_words.c
or/usr/bin/cpp test_count_words.c | indent -kr
for more readable output.What else could be wrong with my clog.h?
Be sure you have parenthesized the condition parameter. The rationale was covered in both sections' lectures. See the slides. Failure to follow this has caused many students' errors and incorrect behavior.I'm confused about
isatty(…)
and/or how to omit the ANSI color codes when output is going to a file or another program. What should I do?isatty(…)
is a function used to check if we are printing to a file or a terminal, which allows us to suppress color codes when printing to a file. This is not a requirement this semester.
You are free to useisatty(…)
to suppress color codes when printing to a file if you wish as a learning exercise, but doing so (or not doing so) will not affect your grade. Make sure the color codes properly print to the console in either case.
Updates
6/30/2022 |
|