Advanced C Programming

Spring 2019 :: ECE 264 :: Purdue University

⚠ This is for Spring 2019, not the current semester.
Due 2/13

Mini-unit: #define + make

Learning goals

You will learn the following concepts/skills:

  1. Understand the C preprocessor and predict its effect
  2. Use #ifdef, #ifndef, and #else to enable/disable functionality based on a compiler flag (gcc -D▒▒▒)
  3. Write C macros for functionality that would be impossible using functions (e.g., printing text of expression)
  4. Create make files for compilation and testing
  5. Use include guards to enable more versatile use of header files.
  6. Print text in color on the terminal using ANSI color codes

Overview

Real-world software projects typically consist of tens, hundreds, or many thousands of files. Testing code by hand (i.e., by playing with it) is useless since there are so many components, and ways things can go wrong. Even compiling such a project can be complex—and 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.

You will create the following:

Coverage of unit testing and build systems will be very light. There is lots more to learn and many more sophisticated options that what we will use here. However, this will get you started with some basic concepts which you will likely be using for the foreseeable future in your programming careers.

Resources

The following are some of the clearest explanations I've seen about some of these topics. Let us know on Piazza if you find any others that you think your classmates would benefit from.

Copying from this page

You may hand-copy any code snippets you find in this homework description into your HW05 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.

Starter code

You will create all header files needed for this homework.

clog.h – Logging library

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 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(stderr, "%s", (msg)) // enabled
#else
#define log_msg(msg)                              // disabled
#endif

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(stderr, "%s", (msg)) // enabled
#else
#    define log_msg(msg)                              // disabled
#endif

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

#ifndef __CLOG_H__
#define __CLOG_H__
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
#endif

See the article linked above for details on include guards.

echo_color

Create the echo_color program as specified in the Requirements table. This will be used in your Makefile later in this homework, but doing it now will give you an opportunity to play with at least part of your clog.h.

Your echo_color.c should use the ANSI_▒▒▒ constants defined in your clog.h. You may use macros from clog.h if you wish.

Tip: You may wish to create helper macros in your clog.h to reduce redundant code. Names of helper macros must start with __mu_ to reduce conflicts with other names.

Your echo_color will not be compiled with gcc -DDEBUG. In other words, echo_color needs to work properly even when compiled without -DDEBUG. That means you probably won't want to call log_red(…) (and the other log_▒▒▒(…)) from echo_color.c. You can use helper macros or other strategies to work around that.

miniunit.h – Testing library

Until now, we have used a very low-tech method of structuring our tests with the expected.txt and the diff command. That method is based on the more general principle of unit testing.

In unit testing, programmers write collections of small functions that test some aspect of a program. For example, in mintf(…), you might have a unit test for small positive integers in base 10, another unit for small negative numbers, one for extreme bases, extreme positive values, extreme negative values, and so on. Each test is its own function.

As the number and complexity of tests grows, it becomes necessary to have a foundation of code for running and organizing them. This is called a unit test framework. There are unit test frameworks for every major programming language. For C, one example is CUnit. These typically have special-purpose macros for checking assumptions, similarly to the assert(…) macro, which we have already covered this semester, but with a unit test framework, we may allow the code to continue, even if an assumption is not met, since the goal is simply to check. In addition to the checking macros, real-world testing frameworks have mechanisms for setting up the needed environment that the program-in-test depends on (e.g., files, network connections, etc.), and sophisticated interfaces for visualizing the results and scheduling test runs.

In this homework, you will create a very simple unit test framework… let's call it a unit test library. You may use this to test future assignments. We will illustrate by example.

Suppose you have the following (very trivial) module for doing arithmetic calculations involving the number 3.

three.c
 1 #include "three.h"
 2
 3 int times_3(int n) {
 4     return n * 3;
 5 }
 6
 7 int divided_by_3(int n) {
 8     return n % 3;  // BUG
 9 }
10
11 int plus_3(int n) {
12     return n * 3;  // BUG
13 }
14
15 int minus_3(int n) {
16     return n - 3;
17 }
three.h
 1 int times_3(int n);
 2 int divided_by_3(int n);
 3 int plus_3(int n);
 4 int minus_3(int n);

Here is the general format for every unit test function (e.g., in test_three.c). You will typically have several of these in your test_▒▒▒.c.

int test_▒▒▒() {
  mu_start();  // set up the test
  // …
  mu_check(condition);
  // …
  mu_check(condition);
  // …
  mu_end();    // finish the test
}

Then, to call all of your unit test functions, you will have a runner function (e.g., main(…)) like this:

int main(int argc, char* argv[]) {
	mu_run(test_▒▒▒);
	mu_run(test_▒▒▒);
	mu_run(test_▒▒▒);
	mu_run(test_▒▒▒);
  return EXIT_SUCCESS;
}

Using the above format, we create the following unit test suite (group of tests) for our three-arithmetic module.

test_three.c
 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include "miniunit.h"
 4 #include "three.h"
 5
 6 int test_plus_3() {
 7     mu_start();
 8     mu_check(plus_3(1) == 4);  // will FAIL
 9     mu_end();
10 }
11
12 int test_minus_3() {
13     mu_start();
14     mu_check(minus_3(1) == -2);
15     mu_end();
16 }
17
18 int test_times_3() {
19     mu_start();
20     mu_check(times_3(1) == 3);
21     mu_end();
22 }
23
24 int test_divided_by_3() {
25     mu_start();
26     mu_check(divided_by_3(1) == 0);  // will FAIL
27     mu_end();
28 }
29
30 int main(int argc, char* argv[]) {
31     mu_run(test_plus_3);        // will FAIL
32     mu_run(test_minus_3);
33     mu_run(test_times_3);
34     mu_run(test_divided_by_3);  // will FAIL
35     return EXIT_SUCCESS;
36 }

Running the test at the command line will produce the following:

Your unit test library, using #define macros will convert that seemingly simple test code into a colored summary, including the name of the test function and the line where it failed. Here is the expected out put for the test above:

$ gcc -o test_three three.c test_three.c

$ ./test_three
Test failed (test_plus_3) at line 8
Test passed (test_minus_3)
Test passed (test_times_3)
Test failed (test_divided_by_3) at line 26

$

Makefile – Build automation

Create a file called Makefile that will be used as input too the make command. This file will have instructions for how to not only compile a program, but also run your tests, submit, run the pretester, and even run a code coverage tester to search for areas of your code that your tests may not be covering.

The Makefile must be specific to a particular project. For , we will create a Makefile for the code you already wrote for HW04. Going forward, you can easily change a few variables at the top of your Makefile to make it work for future assignments.

The structure of a makefile is as follows:

# VARIABLES
LHS=RHS
LHS=RHS


# RULES
target: dependencies…
    action
    action
    

# .PHONY TARGET - declare targets that do not correspond directly to an output file
.PHONY: target_name target_name 

The Makefile must be named “Makefile” (exactly).

The variables section (at the top) contains filenames, and other information that you will use in the commands that build and manage your project. In this homework, we have specified exactly what variables you need in the Requirements table.

The target of a rule is usually a file that will be created by the actions in that rule. For example, the test_mintf rule will create the test_mintf executable file. The echo_color rule will create the echo_color file. Some targets, called phony targets, are essentially just scripts for performing actions related to your project (e.g., submit, run tests, etc.).

The dependencies… are a list of files upon which that rule depends. For example, test_mintf depends on mintf.c, mintf.h. If you were to use your new console log macros in your mintf.c, then clog.h would also be a dependency of test_mintf.

At the end of your Makefile you should have a rule called .PHONY. The dependencies of .PHONY should be every target that is not a real file target.

Running make target from bash runs the specified actions. If the rule has any dependencies, those are built first, if needed.

The first rule in the Makefile will be the default. When you run make from bash (with no arguments), it builds the default target. Calling make target builds the specified target.

The specifics on what your Makefile must contain are in the requirements table. The slides linked from the Resources section above have a good explanation of makefiles.

Creating your Makefile

The following is a suggestion, to help you create your makefile and also understand what you are doing at the same time. (You may hand-copy, but do not copy-paste. You won't learn as well, and it won't work anyway.)

  1. Copy your mintf.c, mintf.h, test_mintf.c, and test_mintf.txt into your HW05 directory. Something like this should work (from your HW05 directory):
    you@ecegrid-thin1 ~/hw05/ $ cp ../hw04/{test_,}mintf.{h,c,txt}

  2. Create a very simple Makefile to just compile test_mintf
    1. Create a blank file called Makefile.
    2. Add one rule to compile test_mintf as follows.
      test_mintf: mintf.c mintf.h clog.h gcc -o test_mintf mintf.c test_mintf.c -g -std=c11 -Wall -Wshadow -Wvla -Werror -pedantic -DNDEBUG
  3. Test the Makefile.
    1. From bash:
      you@ecegrid-thin1 ~/hw04 $ make

    2. From Vim:
      :make
  4. Modify your Makefile to use variables where possible.
    1. Add the CC and CFLAGS variables at the top. (See the Requirements table.) Here's a start:
      EXECUTABLE=test mintf SRC C=mintf.c TEST C=test mintf.c HEADERS H=mintf.h clog.h CC=gcc CFLAGS=-g -std=c11 -Wall -Wshadow -Wvla -Werror -pedantic -DNDEBUG
    2. Modify your rule to use the variables.
      $(EXECUTABLE): $(SRC C) $(TEST C) $(HEADERS H) $(CC) -o test mintf mintf.c test mintf.c $(CFLAGS)
  5. Test the Makefile in the same manner as before.
  6. Add the remaining rules, as specified in the Requirements table. Some additional hints are below.

Tip: make test

To silently compare actual.txt with test_mintf.txt and print a message, depending on whether they match:

@diff -b actual.txt test_mintf.txt &> /dev/null ; \
if [ $$? -eq 0 ]; then                    \
  ./echo_color ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ;     \
else                                      \
  ./echo_color ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ;     \
fi

The backslashes(“\”) in the lines above make them effectively one statement. The semicolons (“;”) indicate the end of a statement. (There should be no semicolon after then or else.) The at-sign (“@”) at the beginning tells make not to print the command itself before executing it.

Tip: make coverage – Code coverage checking with gcov

Code coverage is a way of measuring how thorough your test are—or at least alerting you if there are portions of your code that are not exercised at all by your tests. It is not a guarantee that your tests will catch every flaw, but it can certainly help.

To check code coverage, use this:

# Compile a special version of executable with coverage checking
gcc -o executable source files... -ftest-coverage -fprofile-arcs -DNDEBUG

# Run the executable to record coverage statistics
./executable

# Print a coverage summary
gcov -f mintf.c

For more detailed information, open the file mintf.c.gcov.

Try this yourself from bash, before you do it through your Makefile. You need to see what it is doing, independent of make. Code coverage checking is completely independent of make. Putting it in the Makefile is simply a convenience so you don't have to remember the parameters.

You should see something like this. Your stats will be different. The example below is what you would see if you have not tested adequately. In this case, only 68% of the lines in mintf.c were executed at all. None of the three functions was tested thoroughly. This metric excludes blank lines, comments, and lines containing only braces, so 100% line coverage should be attainable.

you@ecegrid-thin1 ~/HW05 $ gcc -o test_mintf mintf.c test_mintf.c -fprofile-arcs -ftest-coverage -DNDEBUG

you@ecegrid-thin1 ~/HW05 $ ./test_mintf
▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒

you@ecegrid-thin1 ~/HW05 $ gcov -f mintf.c
Function 'mintf' Lines executed:61.29% of 31 Function 'print_integer' Lines executed:75.00% of 8 Function '_print_integer_digit' Lines executed:81.82% of 11 File 'mintf.c' Lines executed:68.00% of 50 Creating 'mintf.c.gcov'

Tip: make clean

make clean should delete the executables (test_mintf and echo_color), actual output (actual.txt), and any data files created by the code coverage check (if any). To delete file(s), use rm -f ▒▒▒. The -f tells the rm command not to prompt for confirmation or print an error if no such file exists.

Be careful not to delete your .c or .h files, or your Makefile.

Tip: Tab (not spaces)

If you are using an editor other than Vim (with the provided .vimrc), be sure your editor is configured to indent with tab characters (not spaces) when editing your Makefile. Make requires that actions be indented with a tab character.

Requirements

  1. Your submission must contain each of the following files, as specified:
    file contents
    echo_color.c function
    main(int argc, char✶ argv[])
    1. Print the second argument (argv[2]) in the color specified by the first argument (argv[1]).
    2. Usage is as follows:
      ./echo_color COLOR STRING
    3. COLOR ∈ "red", "green", "yellow", "blue", "magenta", "cyan"
    4. If user calls with the wrong number of arguments (argc != 3) or if color is not one of the 6 colors above, then print the usage message as follows (exactly):
      Usage: %s <"red"|"green"|"yellow"|"blue"|"magenta"|"cyan"> STRING … where %s is the name of the command as it was called (i.e., argv[0]).
    5. Return EXIT_FAILURE in case of error (i.e., incorrect arguments).
      For HW05, we won't test the return code in case of error, but this is the way C programs—and in fact terminal-based programs written in any language—are expected to operate. You should get into the habit now.
    miniunit.h macros
    mu start()
    1. Declare a local variable and initialize it to 0.
    2. The purpose of this variable is to store the line number of the first "check". (By “check” we mean a call to mu_check(…).)
    3. Initial value should be 0.
    4. Name of variable must begin with __mu_ (e.g., __mu_line_number_of_first_failure).
    mu check(condition)
    1. If condition is false, store the line number of this mu_check(…) call.
      • Store the line number only on the first check that fails.
      • If you have multiple calls to mu_check(…) that fail, you want to keep only the line number of the first one that failed.
    2. You can get the current line number with __LINE__.
    mu run(function)
    1. Call function() (with no parameters).
    2. If it returns 0, then print (in green on stderr):
      Test passed: function
    3. If it returns a line number (≥1), then print (in red on stderr):
      Test failed: function at line line#
    mu end()
    1. Return the line number of the first check (mu_check(…)) the failed.
    2. If all checks succeeded, then return 0.
    clog.h constants
    ANSI color codes
    1. 7 codes: red, green, yellow, blue, magenta, cyan, and reset
    2. The first (ANSI_RED) is provided below.
    3. Search for the rest of the codes online. This is so you can see that these are standad, and not part of ECE 264. As long your source agrees with our ANSI_RED below, the rest should be fine.
    4. const char* ANSI_RED = "\x1b[31m"; // ← OK to copy
    5. const char* ANSI_GREEN = "▒▒▒▒▒▒▒▒";
    6. const char* ANSI_YELLOW = "▒▒▒▒▒▒▒▒";
    7. const char* ANSI_BLUE = "▒▒▒▒▒▒▒▒";
    8. const char* ANSI_MAGENTA = "▒▒▒▒▒▒▒▒";
    9. const char* ANSI_CYAN = "▒▒▒▒▒▒▒▒";
    10. const char* ANSI_RESET = "▒▒▒▒▒▒▒▒";
    macros
    log msg(msg)
    • fprintf(stderr, "%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(stderr, "%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(stderr, "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(c)
    • log_char(ch)fprintf(stderr, "ch == '%c'\n", ch)
    log addr(addr)
    • log_addr(addr)fprintf(stderr, "addr == %p\n", addr)
    • log_addr(&n)fprintf(stderr, "&n == %p\n", &n)
    log red(format, ...)
    • Equivalent to fprintf(stderr, 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
    log yellow(format, ...)
    • Like log_red(…) but in yellow
    log blue(format, ...)
    • Like log_red(…) but in blue
    log magenta(format, ...)
    • Like log_red(…) but in magenta
    log cyan(format, ...)
    • Like log_red(…) but in cyan
    Makefile makefile
    Variable definitions (top section)
    name value (exactly)
    SRC_C mintf.c
    SRC_H mintf.h clog.h miniunit.h
    TEST_C test_mintf.c
    TEST_EXPECTED test_mintf.txt
    TEST_ACTUAL actual.txt
    EXECUTABLE test_mintf
    ASG_NICKNAME HW04
    CC gcc
    CFLAGS -g -std=c11 -Wall -Wshadow -Wvla -Werror -pedantic -DNDEBUG
    CFLAGS_GCOV $(CFLAGS) -fprofile-arcs -ftest-coverage
    Rules
    target name action(s)
    test_mintf Compile the executable.
    test
    Run executable (test_mintf), redirecting stdout to actual.txt.
    If actual.txt matches test_mintf.txt), print…
    Test passed: actual.txt matches test_mintf.txt
    If actual.txt does not match test_mintf.txt, print…
    Test failed: actual.txt does NOT match test_mintf.txt
    submit Submit the assignment using 264submit.
    pretest Pretest the assignment using 264test
    coverage Check the code coverage of your tests.
    debug Compile test_mintf with DEBUG defined.
    echo_color Compile echo_color.
    clean Delete all files created by any of the above rules.
    1. Use the variables wherever possible to avoid duplicating information.
      • Information in the variables (above) should not be duplicated elsewhere.
    2. Use dependencies to reduce duplication and ensure each action has what it needs.
      • A rule should depend (directly or indirectly) on every file it uses.
      • Do not repeat exactly the same compilation command in multiple places.
      • Ex: test should depend on test_mintf and echo_color.
    3. Do not add pointless (unnecessary) dependencies.
      • Ex: make echo_color should not depend on mintf.c or test_mintf because they are not needed to build echo_color. Thus, they are unnecessary for echo_color.
      • Ex: Any rule that depends on test_mintf should not list mintf.c or test_mintf.c as dependencies since they are implied by test_mintf and thus unnecessary.
    4. Do not define DEBUG or NDEBUG in your compilation commands unless otherwise specified in these requirements.
      • make debug: Compile with DEBUG.
      • make coverage: Compile with NDEBUG but not DEBUG to reduce the effect of logging macros (e.g., log_int(…), etc.) and assertions (e.g., assert(…)) on the metrics.
    5. miniunit.h and echo_color.c should use clog.h to the extent possible—at least use the ANSI_▒▒▒ constants.
    6. make submit should result in:
      264submit HW04 mintf.c test_mintf.c test_mintf.txt miniunit.h clog.h
      See Q&A #7.
  2. You may hand-copy any code snippets you find in this homework description into your HW05 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.
  5. Macros in clog.h 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
  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 echo_color.c, Makefile, clog.h, and miniunit.h.
    • In C, you can use isatty(STDERR_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, stderr *.c, *.h
    string.h strcmp echo_color.c
    unistd.h isatty, STDERR_FILENO *.c, *.h
    stdlib.h EXIT_SUCCESS
    Check with the instructor if you are using others. For HW05, we do not anticipate prohibiting any external functions or constants (within reason), but if you find yourself in need of something else, there's a decent chance you are on the wrong track. In particular, you should not need to use printf(…) at all, since all output is going to stderr.
  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 use both.
  11. Submissions must meet the code quality standards and the course policies on homework and academic integrity.
    • That means your echo_color.c and miniunit.h need to work work properly when compiled without -DDEBUG using the same compiler flags as usual (i.e., gcc -g -std=c11 -Wall -Wshadow -Wvla -Werror -pedantic -DNDEBUG).

Submit

To submit HW05, type 264submit HW05 miniunit.h clog.h Makefile echo_color.c from inside your hw05 directory.

Pre-tester

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

Q&A

  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(…)?
    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_stderr(...) fprintf(stderr, __VA_ARGS__)
    That macro would convert printf_to_stderr("%d", 3) to fprintf(stderr, "%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", \
            food)
  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");
    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 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.
    // USE THIS WAY
    #define profess_love_for_two_foods(food1, food2) do { \
        printf("I love %s", food1);                       \
        printf("I love %s", food2);                       \
    } while(false)
  7. Will testing make submit result in a late submission of HW04?
    Yes, it will result in a submission, but it will not harm your grade. See the syllabus section on homework multiple submissions.
  8. How is echo_color supposed to work?
    Here are some examples.
    $ make echo_color
    gcc -std=c99 -g -Wall -Wshadow --pedantic -Wvla -Werror -o echo_color echo_color.c
    
    $ ./echo_color red "I am a tomato."
    I am a tomato.
    
    $ 
    Notice that -DDEBUG was not part of the compilation command above.
    Error cases:
    $ ./echo_color red
    Usage:  ./echo_color <"red"|"green"|"yellow"|"blue"|"magenta"|"cyan"> STRING
    
    $ ./echo_color SALTY "I am soup."
    Usage:  ./echo_color <"red"|"green"|"yellow"|"blue"|"magenta"|"cyan"> STRING
    
    $ ./echo_color
    Usage:  ./echo_color <"red"|"green"|"yellow"|"blue"|"magenta"|"cyan"> STRING
    
    $ 
    Some asked how to handle quoted arguments or spaces in arguments. Bash handles this for you. When a user types a command in bash, it splits that command into arguments and passes the arguments to the program (e.g., your echo_color). If the user places quotes around words (e.g., ./echo_color red "I am a tomato."), bash treats that as one argument (without the quotes).
    Since people asked, here are some examples. Again… You do not need to do anything special fo handle quotation marks or spaces in arguments.

    $ ./echo_color green "I am a pea."
    I am a pea.
    
    $ ./echo_color "green" "I am a pea."
    I am a pea.
    
    $ ./echo_color "green" I\ am\ a\ pea.
    I am a pea.
    
    $ ./echo_color green I\ am\ a\ pea.
    I am a pea.
    
    $ 

Updates

2/8/2019 Corrected filename in requirements table; added details and example about miniunit.h.
2/9/2019 Added requirements and instructions for Makefile.
2/10/2019 Clarifications for Makefile; more information and example about code coverage
2/11/2019 Clarifications: make submit, future use
Added Q&A #7
Correction to make test actions: added ; @ \
Correction: log_int(…), log_char(…), log_addr(…), and log_str(…) should print a newline ('\n').
2/12/2019 Correction to the section about #ifdef/#else/#endif