Advanced C Programming

Spring 2023 ECE 264 :: Purdue University

Alchemy details (also sent last week)
Due 2/9

Log macros

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. #include guards – enable more versatile use of header (.h) files.

Overview

For this part, you will create a reusable library for debugging any C project. When you are done, you will use log_int(…) to print an int variable, log_float(…) to print a float variable, log_str(…) to print a str variable, log_char(…) to print a char variable, log_addr(…) to print any address variable (e.g., char*, int*, etc.), log_bool(…) to print a bool variable, You will use these similarly to the way some people use printf(…), but these macros will generate code that prints the variable name (or expression) as well as the value. For example, invoking log_int(3 + 4) will generate the C code printf("%s == %d\n", "3 + 4", 7), which will print 3 + 4 == 7.

For this part, you will create a reusable library for debugging any C project. In the process, you will learn about the C preprocessor.

Instructions

Get the starter code.

you@ecegrid-thin1 ~ $ cd 264
you@ecegrid-thin1 ~/264 $ 264get hw06
This will write the following file: 1. hw06/test_log_macros.c 2. hw06/expected.txt Ok? (y/n)y 2 files were written
you@ecegrid-thin1 ~/264 $ cd hw06
you@ecegrid-thin1 ~/264/hw06 $

Create log_macros.h

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

Create a new file called log_macros.h and add an include guard to your log_macros.h.

you@ecegrid-thin1 ~/264/hw06 $ gcc test_log_macros.c -o test_log_macros
you@ecegrid-thin1 ~/264/hw06 $ ./test_log_macros
you@ecegrid-thin1 ~/264/hw06 $

Compile and run test_log_macros.c.

you@ecegrid-thin1 ~/264/hw06 $ gcc test_log_macros.c test_log_macros
you@ecegrid-thin1 ~/264/hw06 $ ./test_log_macros

You should see output for every test. (This is abbreviated.)

Test 1 3 + 4 == 7 Test 2 population == 51605 Test 3 'A' == 'A' Test 4 65 == 'A' ✂⋯✂ Test 15 3 > 1 == true

So far, all of the output you see is generated by simple printf(…) statements. As you implement each of the macros for this assignment, you will be creating a test below each of those. When you are done, running should display two copies of each test.

Test 1 3 + 4 == 7 3 + 4 == 7 Test 2 population == 51605 population == 51605 Test 3 'A' == 'A' 'A' == 'A' Test 4 65 == 'A' 65 == 'A' ✂⋯✂ Test 15 3 > 1 == true 3 > 1 == true

Enable tests for log_int(…) and then implement it.

The log_int(…) function-like macro will print an integer value, along with the text of argument to log_int(…).

Open both test_log_macros.c and log_macros.h in tabs.

you@ecegrid-thin1 ~/264/hw06 $ vim test_log_macros.c log_macros.h

Uncomment the tests for log_int(…) in log_macros.h.

Switch to the log_macros.h tab. (Press «Tab» or use your mouse.)

Create a #define macro for log_int(…) in log_macros.h. This should be only one line of code.

Compile and run test_log_macros.c.

you@ecegrid-thin1 ~/264/hw06 $ gcc test_log_macros.c -o test_log_macros
you@ecegrid-thin1 ~/264/hw06 $ ./test_log_macros

This time, the tests for log_int(…) should each appear twice (i.e., one for the simple printf(…) statement and one for the real test). Verify that the output matches exactly. The rest of the tests will all appear only once each.

Test 1 3 + 4 == 7 3 + 4 == 7 Test 2 population == 51605 population == 51605 Test 3 'A' == 'A' Test 4 65 == 'A' ✂⋯✂ Test 15 3 > 1 == true

Enable tests for log_char(…), implement it, and test.

Follow the same procedure you used for log_int(…). The specification for log_char(…) is in the Requirements table. Submit.

Enable tests for log_str(…), implement it, and test.

Follow the same procedure you used for log_int(…). The specification for log_str(…) is in the Requirements table. Submit.

Enable tests for log_addr(…), implement it, and test.

Follow the same procedure you used for log_int(…). The specification for log_addr(…) is in the Requirements table. Submit.

Enable tests for log_float(…), implement it, and test.

Follow the same procedure you used for log_int(…). The specification for log_float(…) is in the Requirements table. Submit.

Enable tests for log_bool(…), implement it, and test.

Follow the same procedure you used for log_int(…). The specification for log_bool(…) is in the Requirements table. Submit.

Test that the complete output matches expected.txt.

First, just compile and run normally, so you can inspect visually.

you@ecegrid-thin1 ~/264/hw06 $ gcc test_log_macros.c -o test_log_macros
you@ecegrid-thin1 ~/264/hw06 $ ./test_log_macros

Compare the output of test_log_macros with expected.txt.

you@ecegrid-thin1 ~/264/hw06 $ diff expected.txt <(./test_log_macros)

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
    log_macros.h macros
    log int(n)
    • log_int(var_name)printf("var_name == printf_code\n", var_name)
    • Ex: log_int(3 + 3) should be expanded by the preprocessor into code that displays this:
      3 + 3 == 6
    log str(s)
    • log_str(var_name)printf("var_name == printf_code\n", var_name)
    • Ex: char* s = "abc";log_str(s) should be expanded by the preprocessor into code that displays this:
      s == "abc"
    • Ex: log_str("abc") should be expanded by the preprocessor into code that displays this:
      "abc" == "abc"
    log float(n)
    • log_float(var_name)printf("var_name == printf_code\n", var_name)
    log char(ch)
    • log_char(var_name)printf("var_name == printf_code\n", var_name)
    log bool(condition)
    • log_bool(condition) prints condition == false if condition is false, or condition == true otherwise.
    log addr(addr)
    • log_addr(var_name)printf("var_name == printf_code\n", var_name)
    • 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
    • When passing an address to printf("… %p …"), you must typecast it to void*.
    test_log_macros.c test code
    main(▒▒▒)
    • Follow the instructions to create this file.
  2. Each macro must be exactly one line.
  3. Do not add any functions.
  4. 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. Correct functioning of your code is your responsibility.
    • Be sure you understand what you are copying.
    • Copying from this page is not necessary. This permission is given as a convenience, since some syntax may be unfamiliar.
  5. You may use any of the following:
    header functions/symbols allowed in…
    stdbool.h bool, true, false *.c, *.h
    stdio.h printf *.c, *.h
    stdlib.h EXIT_SUCCESS test_log_macros.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.
  6. Submissions must meet the code quality standards and the policies on homework and academic integrity, and follow the general intent of the assignment.

Submit

To submit HW06 from within your hw06 directory, type 264submit HW06 log_macros.h test_log_macros.c

Pretester

Q&A

  1. Should I have a semicolon at the end of a #define macro

    No. The person using the macro will normally include the semicolon.
  2. 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).
  3. 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., log_macros.h). true, and false.

Updates

9/23/2022
  • Released.