Eric Radman : a Journal

Trusted Macros in C

One of the design goals of expectations, a unit-test framework for Ruby is to encourage people to Verify One Condition per Test. When programming in C I think this principle needs to be called Verify One Function per Test. Macros are a bit different in that they should be generalized bits of code that prove themselves through tests, reasoning, and even discussion about compiler standards.

If a macro implements application logic, then it should be formulated as a function and unit-tested. If macros are proven by virtue of their origin or some kind of outside reference then a programmer is free to treat them as if they were language primitives that work even on unknown platforms.

Example: MEMBER_LENGTH

In C it isn't possible to read the size of a struct member without a reference to the struct, so this doesn't work

typedef struct {
    int intArray[2];
    char charArray[4];
    double doubleArray[6];
} myStruct;

printf("size of myStruct.intArray is %d\n", sizeof myStruct.intArray);
/*
member.c: In function `main':
member.c:10: error: syntax error before '.' token
*/

myStruct has no spacial charactarisitics until we create an instance. One way to solve this is to make one

myStruct myStructInstance;
printf("size of myStruct.intArray is %d\n", sizeof myStructInstance.intArray);
$ ./a.out
size of myStruct.intArray is 8

It turns out that this value can be calculated at compile time by casting a null pointer and then using sizeof on one of it's elements

printf("size of myStruct.intArray is %d\n", sizeof(((myStruct *)0)->intArray));

This is the sort of code that belongs in a macro because it's useful, strange, and valid. It looks strange because it's not legitimate to dereference a null pointer but it's valid because the calculation is done at compile-time without an instantiated pointer!

#include <stdio.h>

#define LENGTH(x) (sizeof x / sizeof x[0])
#define MEMBER_SIZE(S, M) sizeof(((S *)0)->M)
#define MEMBER_OF(S, M) ((S *)0)->M
#define MEMBER_LENGTH(S, M) LENGTH(MEMBER_OF(S, M))

int main() {
    typedef struct {
        int intArray[2];
        char charArray[4];
        double doubleArray[6];
    } myStruct;

    printf("size of myStruct.intArray is %d\n", MEMBER_SIZE(myStruct, intArray));
    printf("size of myStruct.charArray is %d\n", MEMBER_SIZE(myStruct, charArray));
    printf("size of myStruct.doubleArray is %d\n", MEMBER_SIZE(myStruct, doubleArray));

    printf("length of myStruct.intArray is %d\n", MEMBER_LENGTH(myStruct, intArray));
    printf("length of myStruct.charArray is %d\n", MEMBER_LENGTH(myStruct, charArray));
    printf("length of myStruct.doubleArray is %d\n", MEMBER_LENGTH(myStruct, doubleArray));

    return 0;
}

$ cc struct_len_test.c
$ ./a.out
size of myStruct.intArray is 8
size of myStruct.charArray is 4
size of myStruct.doubleArray is 48
length of myStruct.intArray is 2
length of myStruct.charArray is 4
length of myStruct.doubleArray is 6

After some experimentation it's clear that macros should be demonstrated, explained, and then used. Use automated tests on functions.

Last updated on November 26, 2016