Eric Radman : a Journal

Trusted Macros in C

One of school of thought encourages people to verify one condition per test. When programming in C, we may apply this principle by attempting to 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 is not 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 October 11, 2019