Advanced C Programming

Spring 2021 ECE 264 :: Purdue University

⚠ This is a PAST SEMESTER (Spring 2021).
Due 2/24

Mini-unit 1: preprocessor

Learning goals

You will learn the following concepts/skills:

  1. C preprocessor − how to use several of the most commonly used directives
    1. #define symbols – not just for defining constants
    2. #define macros – like functions but with special capabilities
    3. #ifdef/#ifndef – disable sections of code with a #define or GCC flag
    4. #include guards – enable more versatile use of header (.h) files.
  2. 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:

Starter code

You will create your clog.h from scratch (i.e., starting with a blank file).

We are providing a simple program for counting words that you can use to test your HW06. You will be modifying this program to use clog.h. Later, you will modify it further to use your miniunit.h.

There is also a script called (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.


In a new file called clog.h, create each of the log_▒▒▒(…) macros specified in the Requirements table. We recommend writing them in the order shown (e.g., log_msg(…) first, then log_int(…), and so on).

To test, make a separate test file called test_clog.c with calls to each of the macros.

Use #ifdef DEBUG (and #endif) to ensure that your log_▒▒▒(…) macros only work when the program was compiled with gcc -DDEBUG ▒▒▒. Without gcc -DDEBUG, your program should still compile, but they should have no effect. That can be done by adding an empty macro.

Here is a skeleton as a starting point. You are welcome (and encouraged) to modify this, as you see fit.

#ifdef DEBUG
#define log_msg(msg) fprintf(stdout, "%s", (msg)) // enabled
#define log_msg(msg)                              // disabled

For readability, you may wish to indent like this (below). The # must be the first character on the line. Indenting macros like this is not required by the code quality standards. Use whichever you prefer.

#ifdef DEBUG
#    define log_msg(msg) fprintf(stdout, "%s", (msg)) // enabled
#    define log_msg(msg)                              // disabled

Wrap your entire clog.h in an include guard like this:

#ifndef __CLOG_H__
#define __CLOG_H__

See the article linked above for details on include guards.

Use clog.h in test_count_words.c

  1. In test_count_words.c, modify the printf(…) statements to use log_int(…) instead.
  2. Test.


  1. Your submission must contain each of the following files, as specified:
    file contents
    clog.h constants
    ANSI color codes
    • 7 codes: red, green, yellow, blue, magenta, cyan, and reset
    • The first (ANSI_RED) is provided below.
    • Search for the rest of the codes online. This is so you can see that these are standard, and not part of ECE 264. As long your source agrees with our ANSI_RED below, the rest should be fine.
    • #define ANSI_RED "\x1b[31m" // ← OK to copy
    • #define ANSI_GREEN "▒▒▒▒▒▒▒▒"
    • #define ANSI_YELLOW "▒▒▒▒▒▒▒▒"
    • #define ANSI_BLUE "▒▒▒▒▒▒▒▒"
    • #define ANSI_MAGENTA "▒▒▒▒▒▒▒▒"
    • #define ANSI_CYAN "▒▒▒▒▒▒▒▒"
    • #define ANSI_RESET "▒▒▒▒▒▒▒▒"
    log msg(msg)
    • fprintf(stdout, "%s", msg)
    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) fprintf(stdout, "%s == %d\n", (#n), (n))
      You may copy/adapt that, but you will need a way to selectively enable this (and other log_▒▒▒(…)) macros only when the symbol DEBUG is defined.
    log str(s)
    • log_str(s)fprintf(stdout, "s == \"%s\"\n", s)
    • Calling char* s = "abc"; and then log_str(s) should print this:
      s == "abc"
    • Calling log_str("xyx") should print this:
      "xyx" == "xyx"
    log char(ch)
    • log_char(ch)fprintf(stdout, "ch == '%c'\n", ch)
    log addr(addr)
    • log_addr(addr)fprintf(stdout, "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 like a_n == 0x7fff938d
    • Note: You need a cast when passing an address to printf("… %p …")
    log red(format, ...)
    • Equivalent to fprintf(stdout, format, ...) except text is printed in red.
    • To do this, print ANSI_RED, then call fprintf(…), and finally print ANSI_RESET.
    • The specification for this macro above is not code. For your actual #define you will need something like log_red(...). In the RHS, use __VA_ARGS__. Search the web for “variadic macros” for more information on this.
    log green(format, ...)
    • Like log_red(…) but in green
    • See the warning in log_red(…).
    log yellow(format, ...)
    • Like log_red(…) but in yellow
    • See the warning in log_red(…).
    log blue(format, ...)
    • Like log_red(…) but in blue
    • See the warning in log_red(…).
    log magenta(format, ...)
    • Like log_red(…) but in magenta
    • See the warning in log_red(…).
    log cyan(format, ...)
    • Like log_red(…) but in cyan
    • See the warning in log_red(…).
  2. 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.
  3. Names of helper macros (if any) must begin with “__mu_”.
  4. Do not repeat the ANSI codes anywhere other than in clog.h and Makefile.
  5. Required macros in clog.h (i.e., log_▒▒▒(…)) should work ONLY when the symbol DEBUG is defined (i.e., when you compile with gcc -DDEBUG).
    • Hint: You will need to use some combination of #ifdef, #ifndef, #else, and #define
    • If you use any helper macros (e.g., __mu_log_color(…)), they may work even when DEBUG is not defined.
  6. Macros in miniunit.h should work regardless of the symbols defined (e.g., DEBUG).
  7. Do not print ANSI codes when output is being directed to a file or other application.
    • This applies to all parts of this homework, including test_count_words.c, Makefile, clog.h, and miniunit.h.
    • In C, you can use isatty(STDOUT_FILENO) to determine if the output is going to a real terminal ("TTY"), versus being redirected to a file or something else.
  8. You may use any of the following:
    header functions/symbols allowed in…
    stdbool.h bool, true, false *.c, *.h
    stdio.h fputs, fprintf, {{ stream }} *.c, *.h
    string.h strcmp test_count_words.c
    unistd.h isatty, STDOUT_FILENO, STDERR_FILENO *.c, *.h
    stdlib.h EXIT_SUCCESS test_count_words.c
    Check with the instructor if you wish to use others. If you find yourself in need of something else, there's a decent chance you are on the wrong track. In particular, you do not need printf(…) because we are using fprintf(…).
  9. Code that includes clog.h and/or miniunit.h and uses macros from them must compile and run whether or not DEBUG was defined.
  10. miniunit.h should have #include "clog.h" so that users of miniunit.h don't need to include both.
  11. 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 -DNDEBUG -Wno-unused-function)—and without -DDEBUG. Furthermore, tests using your miniunit.h should work properly with or without -DDEBUG.
  12. 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 -DNDEBUG -Wno-unused-function)—and without -DDEBUG. Furthermore, tests using your miniunit.h should work properly with or without -DDEBUG.
  13. 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.
  14. Indent your macros similarly to regular C code. Your code must be readable.


To submit HW06 from within your hw06 directory, type 264submit HW06 clog.h test_count_words.c

Be sure to submit test_count_words.c (as well as clog.h the others).


The pre-tester for HW06 has not yet been released. As soon as it is ready, this note will be changed.


  1. Shouldn't a Makefile refer to .o files

    We are using makefiles in a simplified manner that does not require awareness of .o files.
  2. How do I print a double quotation mark using printf(…)

  3. How do I use a variadic macro to pass arguments from log_red(…) to fprintf(…)

    In the LHS of the #define, use “...” to indicate 1 or more arguments. In the RHS, use __VA_ARGS__ to pass those same arguments to the next function (e.g., fprintf(…)). Note that “...” stands for one or more, not zero or more.
    Here's a simple example:
    #define printf_to_stdout(...) fprintf(stdout, __VA_ARGS__)
    That macro would convert printf_to_stdout("%d", 3) to fprintf(stdout, "%d", 3).
  4. Should I have a semicolon at the end of the RHS of a #define macro

    No. The person using the macro will normally include the semicolon.
  5. How can I continue the RHS of a #define macro definition onto the next line

    Put a backslash ("\") at the end of the line.
    #define profess_love_for_food(food) printf("I love %s", \
  6. 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 the if 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");
        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 a do { … } while(false) block. Because the do…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.
    #define profess_love_for_two_foods(food1, food2) do { \
        printf("I love %s", food1);                       \
        printf("I love %s", food2);                       \
    } while(false)
  7. What is HW55? Is that a typo

    HW55 is a dummy assignment that you will use to test the rules in your Makefile that deal with assignment submission and pretesting. It will not be scored and will not affect your grade.
    HW06 is the “real” assignment you are doing here.
  8. Why does miniunit.h need mu_start(…) and mu_end(…)

    These give a relatively clean and consistent form to your unit tests, so you can focus on the code that matters for each particular test.
    Without mu_start(…) and mu_end(…), one might resort to a naïve approach, like this:
    int test_count_words_▒▒▒▒▒() { int test__mu_failure_line_num = __MU_SUCCESS; mu_check(▒▒▒▒▒▒▒▒▒▒); mu_check(▒▒▒▒▒▒▒▒▒▒); mu_check(▒▒▒▒▒▒▒▒▒▒); return __mu_failure_line_num; }
    The programmer should not have to know the specific internal names used by miniunit.h! If they get it wrong, then it won't work properly with mu_check(…).
    Of course, they could avoid the problem of consistency if they do the whole thing without any macros—i.e., without miniunit.h—but then the above skeleton would be even messier!
    int test_count_words_▒▒▒▒▒() { const int __MU_SUCCESS = 0; int __mu_failure_line_num = __MU_SUCCESS; if(!(▒▒▒▒▒▒▒▒▒▒) && __mu_failure_line_num == __MU_SUCCESS) { __mu_failure_line_num = __LINE__; } if(!(▒▒▒▒▒▒▒▒▒▒) && __mu_failure_line_num == __MU_SUCCESS) { __mu_failure_line_num = __LINE__; } if(!(▒▒▒▒▒▒▒▒▒▒) && __mu_failure_line_num == __MU_SUCCESS) { __mu_failure_line_num = __LINE__; } return __mu_failure_line_num; }
    With mu_start(…) and mu_end(…)—and adding a divider comment (optional)—you get this:
    int test_count_words_▒▒▒▒▒() { mu_start(); //──────────────────────────────────────── mu_check(▒▒▒▒▒▒▒▒▒▒); mu_check(▒▒▒▒▒▒▒▒▒▒); mu_check(▒▒▒▒▒▒▒▒▒▒); //──────────────────────────────────────── mu_end(); }
    mu_start(…) and mu_end(…) also make your unit testing library more extensible. If you wanted to change how your tests are organized and/or reported, you could do so without changing all of your test code that uses it.
  9. How do I convert the printf(…) statements in test_count_words.c to use log_int(…)

    Here is an unrelated example that prints the output of a function using printf(…).
    #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 use log_int(…).
    #include <stdio.h>
    #include <stdlib.h>
    #include "clog.h"
    int triple(int n) {
        return n * 3;
    int main(int argc, char* argv[]) {
        // GOOD
        // Advantages over raw printf(…)
        // ∙ Easy to "turn off" when it is time to submit your code.
        // ∙ Less duplication means fewer oppportunities for bugs.
        return EXIT_SUCCESS;
    With log_int(…), you can print the expression itself, along with its value.
  10. What does #x do in a #define macro

    It expands to the text of the expression, instead of its value. This is easiest to see if you test using the /usr/bin/cpp command.
    Here is an example, which uses the log_int(…) snippet given in the Requirements table.
    // demonstrate_hash.c
    #include <stdio.h>
    #include <stdlib.h>
    #define log_int(n) fprintf({{ stream }}, "%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.
    you@ecegrid-thin1 ~/HW06 $ /usr/bin/cpp demonstrate_hash.c -DDEBUG | indent -kr
    … int main(int argc, char* argv[]) { fprintf({{ stream }}, "%s == %d\n", ("3 + 3"), (3 + 3)); return 0; }
    Notice that the third argument to fprintf(…) is a string literal, "3 + 3"—the text of the argument that was passed to log_int(…). That is different from the fourth argument, which is the value of that parameter, 3 + 3 (= 6).
  11. How do I test my clog.h

    Here's an example. As with all other code snippets in this homework description (for HW06 only), you may hand-copy or adapt this. You may also test in some other way of your choice.
    // test_clog.c
    #include <stdlib.h>
    #include "clog.h"
    int main(int argc, char* argv[]) {
        log_msg("SHOPPING LIST\n");
        log_green("bok choy x %d bunches\n", 3);
        log_red("tomatos x %d\n", 5);
        log_yellow("lemon x %d\n", 3);
        log_blue("borage x %d flowers\n", 100);
        log_magenta("rambutan x %d\n", 15);
        log_cyan("Peeps x %d boxes\n", 5);
        return EXIT_SUCCESS;
    When you compile with -DDEBUG, the output should look like this:
    you@ecegrid-thin1 ~/HW06 $ gcc -o test_clog test_clog.c -DDEBUG
    you@ecegrid-thin1 ~/HW06 $ ./test_clog
    COLORS green red yellow blue magenta cyan SHOPPING LIST bok choy x 3 bunches tomatos x 5 lemon x 3 borage x 100 flowers rambutan x 15 Peeps x 5 boxes
    you@ecegrid-thin1 ~/HW06 $
    When you compile without -DDEBUG, there should be no output, like this:
    you@ecegrid-thin1 ~/HW06 $ gcc -o test_clog test_clog.c
    you@ecegrid-thin1 ~/HW06 $
  12. How can I stop ANSI codes when redirecting to a file or another program

    This refers to the line in the Requirements table that states, “Do not print ANSI codes when output is being directed to a file or other application.”
    As mentioned just below that requirement, you can use the isatty(STDOUT_FILENO) function. It returns true if the output is going directly to the terminal, and false if the output is being redirected to a file or another program.
    The following snippet illustrates how that would work. This is one of several ways you could this. This is included only to help those who may be stuck on this part.
    #define __mu_log_color(color, ...)         \
    do {                                       \
        if(isatty(STDOUT_FILENO)) {            \
            fprintf(stdout, "%s", color);      \
        }                                      \
        fprintf(stdout, __VA_ARGS__);          \
        if(isatty(STDOUT_FILENO)) {            \
            fprintf(stdout, "%s", ANSI_RESET); \
        }                                      \
    } while(false) // unavoidable hack for function-like macros (See Q6.)
    Then, each of your log_COLOR(…) functions would follow the following form:
    #define log_red(...) __mu_log_color(ANSI_RED, __VA_ARGS__)
    … and so on.

    Correction: There should be no semicolon at the end of the last line in that snippet. It should be } while(false) not } while(false); .

  13. How can I test that it is working (i.e., no ANSI codes when output is redirected to a file or another program)

    One way to check is to redirect the output to a file and then open that file in Vim, or print its contents using the cat command.
    you@ecegrid-thin1 ~/HW06 $ gcc -o test_clog test_clog.c -DDEBUG
    you@ecegrid-thin1 ~/HW06 $ ./test_clog &> actual.txt
    you@ecegrid-thin1 ~/HW06 $ cat actual.txt
    COLORS green red yellow blue magenta cyan SHOPPING LIST bok choy x 3 bunches tomatos x 5 lemon x 3 borage x 100 flowers rambutan x 15 Peeps x 5 boxes
    you@ecegrid-thin1 ~/HW06 $
    Another way to check this is to redirect the output to the cat command.
    you@ecegrid-thin1 ~/HW06 $ gcc -o test_clog test_clog.c -DDEBUG
    you@ecegrid-thin1 ~/HW06 $ ./test_clog | cat
    COLORS green red yellow blue magenta cyan SHOPPING LIST bok choy x 3 bunches tomatos x 5 lemon x 3 borage x 100 flowers rambutan x 15 Peeps x 5 boxes
    you@ecegrid-thin1 ~/HW06 $
  14. 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

    This will be a small part of the score. If it isn't working for you, get the other parts working before worrying about this.
  15. Why is this even a requirement

    Vim (and other editors) can call make directly, and show you the output right in the editor. However, the color codes make a mess. More generally, this issue comes up in many programs that print in color (e.g., ls); not printing the ANSI codes to a non-terminal is standard behavior for command-line programs.
  16. How do I make mu_run(…) print the success/failure messages in color even when the DEBUG symbols is not defined (i.e., -DDEBUG not passed to GCC), and without duplicating the ANSI codes in miniunit.h

    Define a helper macro for printing in color outside the #ifdef/#endif. You can use the one in Q12 or make your own. You will need to define the ANSI codes (#define ANSI_RED …, etc.) outside the #ifdef/#endif, as well.
  17. GCC: “ISO C99 requires rest arguments to be used.” ⋯??

    In variadic macros (see Q3), the ... can stand for 1 or more arguments. If your log_red(…) looks like #define log_red(format, ...) ▒▒▒▒▒▒▒▒▒▒, it will likely work as long as you pass ≥2 arguments (e.g., log_red("I like %d", 5)), but if you pass only a string (e.g., log_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). The correct form was given in Q3 and Q12.
  18. GCC: “error: ‘true’ undeclared” ⋯???
    GCC: “error: ‘false’ undeclared” ⋯??

    The true and false 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 about bool, true, and false.
  19. GCC: “implicit declaration of function ‘isatty’” ⋯???
    GCC “‘STDOUT_FILENO’ undeclared” ⋯??

    The isatty(…) function and STDOUT_FILENO symbol are defined in a standard header called unistd.h you need to include it (i.e., #include <unistd.h>). This was communicated in #7 and #8 of the Requirements table.
  20. What is the purpose of the debug rule in the Makefile

    Compiling with make debug will enable your log_▒▒▒(…) macros by passing the -DDEBUG flag to GCC (i.e., to define the DEBUG symbol). With just make (or make test_count_words) DEBUG will not be defined, so your log_▒▒▒(…) macros will be silent. How you call make determines whether they are on or off.
    After HW06, you are welcome to modify this (e.g., if you want your log_▒▒▒(…) macros to be enabled by default.)
  21. Make: “No rule to make target `miniunit.h', needed by `debug'”

    You do not have a file called miniunit.h, but it is listed as a prerequisite of that file.
  22. After editing miniunit.h or clog.h, nothing changes when I run make test

    Make sure your make test rule depends on debug.
  23. GCC: “__mu_failure_line_num is not defined” ⋯ Why

    If you get that within main(…), make sure you are not trying to use the variable declared in mu_start(…) in your mu_run(…). Your mu_start(…) is expanded in the context of a _test_▒▒▒(…) function, while mu_run(…) is expanded in the context of main(…).
  24. 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. It will make the errors more obvious. Then, be sure to use /usr/bin/cpp test_count_words.c | indent -kr or /usr/bin/cpp test_count_words.c -DDEBUG | indent -kr to make the output readable.
  25. After changing the printf(…) statements in test_count_words.c to log_int(…), running make test prints the messages and the test itself doesn't work right.

    It's okay. log_int(…) prints to stdout so the diff testing isn't seeing the output. It is going to the terminal. (This answer might be expanded later.)
  26. What else could be wrong with my clog.h?

    Make 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.


Clarifications or corrections may be posted here.
  • Corrected 264submit … command. (Removed Makefile and miniunit.h).