Advanced C Programming

Spring 2022 ECE 264 :: Purdue University

⚠ This is a PAST SEMESTER (Spring 2022).
Due 2/21

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

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
This will write the following files: 1. hw06/count_words.c 2. hw06/count_words.h 3. hw06/expected.txt 4. hw06/print256colours.sh 5. hw06/test_count_words.c Ok? (y/n)y 5 files were written
you@ecegrid-thin1 ~/264 $ cd hw06
you@ecegrid-thin1 ~/264/hw06 $

Create test_clog.c with a test for logf_(…)

Create a test file called test_clog.c.

you@ecegrid-thin1 ~/264/hw06 $ vim test_clog.c

The logf_(…) function-like macro will behave exactly like printf(…), except that logf_(…) will do nothing when your clog.h macros have been disabled. (Ability to disable the macros will be added in a later step.)

Note: The trailing underscore in logf_(…) is not a typo. See the Q&A (“Why does logf_(…) end with an underscore (‘_’)? …”).

Add a test for the logf_(…) function-like macro as follows. Please hand-copy this code.

#include <stdlib.h>
#include "clog.h"

int main(int argc, char* argv[]) {
  char* city_name = "Bogota";
  int population_pct = 15;
  logf_("%d%% of Columbians live in %s.\n", population_pct, city_name);
  return EXIT_SUCCESS;
}

Create clog.h

you@ecegrid-thin1 ~/264/hw06 $ vim clog.h

Add an include guard to your clog.h. Include guards were covered in class on Thu 2/10/2022.

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 logf_(…)

Add a function-like macro logf_(…) to clog.h. It should behave exactly like printf(…).

Hint: This part can be very simple. You do not need __VA_ARGS__—or even parentheses—for this part (though you may use them if you wish). A similar example was given toward the beginning of class on Tue 2/8/2022.

Test logf_(…).

Compile and run your test_clog.c. You should get the following output.

you@ecegrid-thin1 ~/264/hw06 $ gcc test_clog.c -o test_clog
you@ecegrid-thin1 ~/264/hw06 $ ./test_clog
15% of Columbia lives in Bogota.
you@ecegrid-thin1 ~/hw06 $

Modify clog.h so that logf_(…) does nothing if NDEBUG is defined.

Modify your clog.h so that whenever the NDEBUG symbol is defined (i.e., program is compiled with gcc -D NDEBUG …), your logf_(…) function-like macro is disabled (i.e., calls to logf_(…) do nothing). Programs should still compile with or without -D NDEBUG.

Conditional compilation using #ifndef was covered in lecture on Thu 2/10/2022.

Hint #1: Inside your include guard, you will have something like this:

#ifndef NDEBUG
    // Macros
    //▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
#else
    // DUMMY macros
    //▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
#endif

Hint #2: Inside the #else clause, you will have a dummy macro that makes every call to logf_(…) disappear, leaving only the trailing semicolon. To ensure that your dummy macro replaces logf_(…) calls with any number of arguments, use ... for its parameter list. Thus, your dummy macro will be #define logf_(...). That's it. An example of a dummy macro was given in lecture on Thu 2/10/2022.

Test logf_(…) with and without NDEBUG defined.

Compile and run your test_clog.c with -D NDEBUG.

You will most likely see an error from GCC complaining that you have unused variables.

you@ecegrid-thin1 ~/hw06 $ gcc test_clog.c -D NDEBUG -o test_clog
test_clog.c: In function ‘main’: test_clog.c:8:6: warning: unused variable ‘population_pct’ [-Wunused-variable] int population_pct = 15; ^~~~~~~~~~~~~~ test_clog.c:7:8: warning: unused variable ‘city_name’ [-Wunused-variable] char* city_name = "Bogota"; ^~~~~~~~~
you@ecegrid-thin1 ~/hw06 $

That happens because the variables population_pct and city_name were declared, yet when all calls to logf_(…) were removed by #ifdef, those variables were no longer used anywhere.

Unused variables serve no purpose, so if they are present in real code, it is probably because the programmer made a mistake. GCC treats unused variables as an error to warn you of that hazard.

For HW06, you may silence that error by adding the -Wno-unused-variable argument to your GCC command when testing with -D NDEBUG. We will do the same when scoring HW06 submissions—but only for HW06, and only when testing with -D NDEBUG.

There should be no output when you run your test_clog.c.

you@ecegrid-thin1 ~/hw06 $ gcc test_clog.c -D NDEBUG -Wno-unused-variable -o test_clog
you@ecegrid-thin1 ~/hw06 $ ./test_clog
you@ecegrid-thin1 ~/hw06 $

Confirm that your code still works without the -D NDEBUG flag.

you@ecegrid-thin1 ~/hw06 $ gcc test_clog.c -o test_clog
you@ecegrid-thin1 ~/hw06 $ ./test_clog
15% of Columbia lives in Bogota.
you@ecegrid-thin1 ~/hw06 $

Add a test for logf_red(…)

The logf_red(…) function-like macro will work just like logf_(…), except that text will be printed in red to the terminal.

Add a test for the logf_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.

logf_red("%s\n", "RED");

Implement logf_red(…)

Add the implementation for logf_red(…) to your clog.h.

It should do three things:

  1. Print the ANSI code for red (\x1b[31m) to tell the terminal to print the subsequent text in red.
  2. Pass all arguments to your existing logf_(…).
  3. 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. Use the do/while(false) pattern that we covered in lecture on Thu 2/10/2022.

Test everything up to logf_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
15% of Columbia lives in Bogota. RED
you@ecegrid-thin1 ~/hw06 $

Modify clog.h to disable coloring whenever output is not to a terminal.

When output is redirected to a file or another program, the color codes will not typically be handled properly. Therefore, we will disable the color whenever output is not sent directly to the terminal.

The isatty(…) function checks to see if a stream is a terminal. Normal output to the terminal is called stdout, and is one particular stream. (Output to a file would be another example of a stream.) isatty(…) takes a file number for a stream as input. The file number for stdout is identified as STDOUT_FILENO.

To check if normal program output (stdout) is being sent directly to the terminal, you can use isatty(STDOUT_FILENO).

You must include the standard headers unistd.h and stdio.h at the top of your clog.h.

Edit your logf_red(…) so that the two ANSI codes (for red and reset) are printed only when normal program output (stdout) is being sent directly to the terminal.

Your logf_red(…) will now follow this process (slightly modified from the earlier step).

  1. Print the ANSI code for red (\x1b[31m) to tell the terminal to print the subsequent text in red—but only if the normal program output (stdout) is being sent directly to the terminal.
  2. Pass all arguments to your existing logf_(…).
  3. Print the ANSI code for reset (\x1b[0m) to tell the terminal to stop printing text in red—but only if the normal program output (stdout) is being sent directly to the terminal.

Hint #1: The logic for checking if normal program output (stdout) is being sent directly to the terminal will be inside your do { … } while(false) using a normal if(…) {…}. You are editing the logf_red(…) that you already have—not adding stuff around it.

Hint #2: If you use your logf_(…) to print the ANSI codes, then you do not need to create a dummy macro for logf_red(…); you won't need to add any new #ifndef/#endif for logf_red(…). You should not need to use printf(…) at all in logf_red(…). The only place you need printf(…) in clog.h is one time in your definition of logf_(…).

Test with the modifications to disable color for non-terminal output.

To test the case when normal program output (stdout) is not being sent directly to a terminal, compile and run test_clog.c and redirect the output to the cat command in bash. The cat command just prints whatever output is sent to it. However, redirecting the output to cat will cause isatty(STDOUT_FILENO) to return false.

you@ecegrid-thin1 ~/hw06 $ gcc test_clog.c -o test_clog
you@ecegrid-thin1 ~/hw06 $ ./test_clog | cat
15% of Columbia lives in Bogota. RED
you@ecegrid-thin1 ~/hw06 $

Test the case where normal program output (stdout) is being sent directly to the terminal. This is just to confirm that you haven't broken anything.

you@ecegrid-thin1 ~/hw06 $ gcc test_clog.c -o test_clog
you@ecegrid-thin1 ~/hw06 $ ./test_clog
15% of Columbia lives in Bogota. RED
you@ecegrid-thin1 ~/hw06 $

Add tests for the other logf_«color»(…) function-like macros to test_clog.c.

Add test for the logf_green(…), logf_yellow(…), logf_blue(…), logf_magenta(…), and logf_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 logf_«color»(…) function-like macros.

Add function-like macros for logf_green(…), logf_yellow(…), logf_blue(…), logf_magenta(…), and logf_cyan(…) to your test_clog.c

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 logf_«color»(…) function-like macros.

you@ecegrid-thin1 ~/hw06 $ gcc test_clog.c -o test_clog
you@ecegrid-thin1 ~/hw06 $ ./test_clog
15% of Columbia lives in Bogota. RED GREEN YELLOW BLUE MAGENTA CYAN
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);
log_int(population_pct);

Implement log_int(…).

Add a function-like macro for log_int(…) in your clog.h. This will be very similar to the example given in lecture on Tue 2/8/2022. You may copy/adapt that code (Spring 2022 only).

Test everything up to log_int(…).

you@ecegrid-thin1 ~/hw06 $ gcc test_clog.c -o test_clog
you@ecegrid-thin1 ~/hw06 $ ./test_clog
15% of Columbia lives in Bogota. RED GREEN YELLOW BLUE MAGENTA CYAN 3 + 4 == 7 population_pct == 15
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);
log_char(city_name[0]);

Correction: All three macro invocations above should use log_char(…).

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
15% of Columbia lives in Bogota. RED GREEN YELLOW BLUE MAGENTA CYAN 3 + 4 == 7 population_pct == 15 'A' == 'A' 65 == 'A' city_name[0] == 'B'
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(city_name) should print city_name == "Bogota" .

Add the following tests for log_str(…). Please hand-copy this code.

log_str(city_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
15% of Columbia lives in Bogota. RED GREEN YELLOW BLUE MAGENTA CYAN 3 + 4 == 7 population_pct == 15 'A' == 'A' 65 == 'A' city_name[0] == 'B' city_name == "Bogota"
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. This was covered in the video lecture

Ex: log_addr(city_name) should print city_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(city_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
15% of Columbia lives in Bogota. RED GREEN YELLOW BLUE MAGENTA CYAN 3 + 4 == 7 population_pct == 15 'A' == 'A' 65 == 'A' city_name[0] == 'B' city_name == "Bogota" city_name == 0x400990
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
15% of Columbia lives in Bogota. RED GREEN YELLOW BLUE MAGENTA CYAN 3 + 4 == 7 population_pct == 15 'A' == 'A' 65 == 'A' city_name[0] == 'B' city_name == "Bogota" city_name == 0x400990 1.0 / 8.0 == 0.1250000000000000
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
15% of Columbia lives in Bogota. RED GREEN YELLOW BLUE MAGENTA CYAN 3 + 4 == 7 population_pct == 15 'A' == 'A' 65 == 'A' city_name[0] == 'B' city_name == "Bogota" city_name == 0x400990 1.0 / 8.0 == 0.1250000000000000 3 > 5 == false 3 > 1 == true
you@ecegrid-thin1 ~/hw06 $

Test with the normal program output (stdout) not being directly sent to the terminal. We will redirect the output to the cat command. We expect to see the same text as before, but without the colors.

you@ecegrid-thin1 ~/hw06 $ gcc test_clog.c -o test_clog
you@ecegrid-thin1 ~/hw06 $ ./test_clog | cat
15% of Columbia lives in Bogota. RED GREEN YELLOW BLUE MAGENTA CYAN 3 + 4 == 7 population_pct == 15 'A' == 'A' 65 == 'A' city_name[0] == 'B' city_name == "Bogota" city_name == 0x400990 1.0 / 8.0 == 0.1250000000000000 3 > 5 == false 3 > 1 == true
you@ecegrid-thin1 ~/hw06 $

Test with the NDEBUG symbol defined. This should disable all of the log▒▒▒(…) function-like macros (i.e., cause them to do nothing). We expect to see no output at all from test_clog.

you@ecegrid-thin1 ~/hw06 $ gcc test_clog.c -o test_clog -D NDEBUG -Wno-unused-variable
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?

partially redacted screenshot of solution

Requirements

  1. Your submission must contain each of the following files, as specified:
    file contents
    clog.h macros
    logf (▒▒▒)
    • printf(▒▒▒)
    logf red(▒▒▒)
    • Equivalent to logf_(▒▒▒) except text is printed in red.
    • The specification for this macro above is not code. For your actual #define you will need something like logf_red(...). In the RHS, use __VA_ARGS__. Search the web for “variadic macros” for more information on this.
    logf green(format, ...)
    • Like logf_red(…) but in green
    • See the warning in logf_red(…).
    logf yellow(format, ...)
    • Like logf_red(…) but in yellow
    • See the warning in logf_red(…).
    logf blue(format, ...)
    • Like logf_red(…) but in blue
    • See the warning in logf_red(…).
    logf magenta(format, ...)
    • Like logf_red(…) but in magenta
    • See the warning in logf_red(…).
    logf cyan(format, ...)
    • Like logf_red(…) but in cyan
    • See the warning in logf_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, but you will need a way to selectively disable this (and other log▒▒▒(…)) macros when the symbol NDEBUG is defined.
    log str(s)
    • log_str(s)printf("s == \"%s\"\n", s)
    • char* s = "abc"; log_str(s); should print
      s == "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 like a_n == 0x7fff938d
    • Note: When passing an address to printf("… %p …"), you must typecast it to void*.
    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 of printf(…)
  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. Required macros in clog.h should do nothing whenever a NDEBUG symbol is defined (i.e., code compiled with gcc -D NDEBUG or #define NDEBUG at top of code).
  5. Print ANSI codes only when {{ kind_of_output }} output ({{ stream }}) is being sent directly to the terminal.
  6. 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
    unistd.h isatty, STDOUT_FILENO *.c, *.h
    stdlib.h EXIT_SUCCESS test_count_words.c, test_clog.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.
  7. 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 without -D NDEBUG. Furthermore, tests using your miniunit.h should work properly with or without -D NDEBUG.
  8. 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.
  9. Indent your macros similarly to regular C code. Your code must be readable.
  10. Code that includes clog.h and uses macros from them must compile and run whether or not NDEBUG was defined.

Submit

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

Pretester

Q&A

  1. How do I use a variadic macro to pass arguments from logf_red(…) to printf(…)

    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., 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 convert my_printf("%d", 3) to printf("%d", 3).
  2. 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.
  3. 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)
  4. 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)
  5. 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
        log_int(triple(5));
        log_int(triple(4));
        log_int(triple(3));
        log_int(triple(2));
        log_int(triple(1));
        // 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.
  6. 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 264cpp (or /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) 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 to printf(…) is a string literal, "3 + 3"—the text of the argument that was passed to log_int(…). That is different from the third argument, which is the value of that parameter, 3 + 3 (= 6).
  7. 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.
  8. Why is this even a requirement

    Vim (and other editors) can help you compile your code directly—i.e., without leaving your editor—and show you the output right in the editor. However, the color codes are not interpreted, resulting in 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.
  9. Why does logf_(…) end with an underscore (‘_’)? Is that a typo?

    It is not a typo. The trailing underscore is to avoid conflicting with the standard library function (…) (calculate logarithm on a floating point value). This is inspired by the naming convention in PEP 8 , the official style guide for Python.
  10. GCC: “ISO C99 requires rest arguments to be used.” ⋯??

    In variadic macros (see Q1), the ... can stand for 1 or more arguments. If your logf_red(…) looks like #define logf_red(format, ...) ▒▒▒▒▒▒▒▒▒▒, it will likely work as long as you pass ≥2 arguments (e.g., logf_red("I like %d", 5)), but if you pass only a string (e.g., logf_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).
  11. 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.
  12. 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.
  13. 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 use 264cpp test_count_words.c or /usr/bin/cpp test_count_words.c | indent -kr or /usr/bin/cpp test_count_words.c -D NDEBUG | indent -kr for more readable output.
  14. 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.

Updates

2/15/2022
  • log▒(…) macros enabled by default.
  • log▒(…) output to stdout.
  • gcc ▒▒▒ -D NDEBUG to disable log▒(…) macros.
  • Rename log_red(…) to logf_red(…), and likewise for the other colors.
  • Remove log_msg(…).
  • Add logf(…), log_bool(…), and log_float(…).
  • Step-by-step instructions were added.
  • test_clog.c was added.
Note: The “preview” version of this homework, which was posted until 2/14/2022, warned that “There might be MAJOR changes to the specification.” We made that version visible on the web site to help students understand the context of the lessons about the preprocessor, even though we were actively working on a revision. This revision took some time to release because it entailed deep changes to HW07 and HW08 (i.e., stdout instead of stderr and -D NDEBUG to disable log▒(…) macros instead of -D DEBUG to enable them). The changes were intended to make the experience of completing HW06 smoother, while also making your resulting code more useful in the coming assignments.
2/17/2022
  • Minor corrections and clarifications
2/18/2022
  • Minor corrections and clarifications
2/21/2022
  • Minor corrections and clarifications
2/22/2022
  • Minor corrections and clarifications