cansi-cprecompile

Call function in pre-compile time


Is it possible call a function inside a macro, due to resolve the field in pre-compile time?

For example, I have a structure with 2 fields, a constant char array (a string) and a uint32_t variable that should be the CRC32 of the string, something like that:

#define NEW_ROW(str) \
    {.theString = str, .theCRC = PRECOMPILER_CRC32_FUNCTION(str)}

typedef struct {
    const char *theString;
    const uint32_t theCRC;
} t_MyTable;

const t_MyTable MyTable[] = {
    NEW_ROW("First string"),
    NEW_ROW("Second string"),
    NEW_ROW("Third string"),
    NEW_ROW("Fourth string")
};

I use Cmake compiler.

Is it possible teach the precompiler for call PRECOMPILER_CRC32_FUNCTION(str) before the compilation?


Solution

  • It is not possible for a C program to call a C function at compile time, but it is possible to write a program to generate some C source code, run that program and then compile it.

    For example, the following C program gentable can generate the lines of the table. For convenience, I used the crc32 function from Zlib, hence the #include <zlib.h>, and the program needs to be linked with the z library:

    gentable.c

    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <inttypes.h>
    #include <zlib.h>
    
    #define MYSTRINGS \
        X("First string") \
        X("Second string") \
        X("Third string") \
        X("Fourth string")
    
    static void genline(FILE *fp, const char *asis, const char *binary)
    {
        size_t len = strlen(binary);
        uint32_t crc = crc32_z(0, (const unsigned char *)binary, len);
    
        fprintf(fp, "{ %s, 0x%"PRIx32" },\n", asis, crc);
    }
    
    
    static const char *progname = "gentable";
    
    int main(int argc, char **argv)
    {
        FILE *fp;
    
        if (argc) {
            progname = argv[0];
        }
        if (argc > 2) {
            fprintf(stderr, "usage: %s [OUTPUTFILE]\n", progname);
            exit(2);
        }
        if (argc > 1) {
            fp = fopen(argv[1], "w");
            if (fp == NULL) {
                perror(argv[1]);
                exit(1);
            }
        } else {
            fp = stdout;
        }
    #define X(s) genline(fp, #s, s);
        MYSTRINGS
    #undef X
        if (fp != stdout) {
            fclose(fp);
        }
    }
    

    Some interesting details:

    1. The macro MYSTRINGS includes all the string literals with each string literal used as the parameter of the macro X(s). The macro X(s) is defined further down at the point it is required. This is known as the "X macro" technique.
    2. The macro X(s) is defined as #define X(s) genline(fp, #s, s);. This uses the preprocessor # operator to stringify the argument s. Since it is called with a string literal, it will produce a string literal representation of the source string literal. For example, the argument "First string" will be converted to the string literal "\"First string\"".
    3. The genline function is called with a FILE * stream pointer, the string literal representation of the string literal, and the compiled binary representation of the string. The CRC code is generated from the compiled binary representation of the string. It prints the string literal representation of the string and the CRC code as a line of text of the form { "some string", 0xdeadbeef },
    4. The main() function expands the MYSTRINGS macro to a sequence of calls to the genline() function, which will output a row of source code for the string. The expansion of the X(s) macro ends with a semicolon to separate these calls to the genline() function.

    The program has an optional OUTPUTFILE command-line argument. If present, the output will the written to the named file. For example, this will write the output to file my_table.cdata:

    $ ./gentable my_table.cdata
    

    Here is the output from the program in generated source file my_table.cdata:

    { "First string", 0x7c5b638b },
    { "Second string", 0xc2de7700 },
    { "Third string", 0xe978905f },
    { "Fourth string", 0x7b9353d8 },
    

    The file my_table.cdata can be #included into the program source for your program to fill in the contents of the MyTable[] array:

    demo.c

    #include <stdio.h>
    #include <inttypes.h>
    
    typedef struct {
        const char *theString;
        const uint32_t theCRC;
    } t_MyTable;
    
    const t_MyTable MyTable[] = {
    #include "my_table.cdata"
    };
    
    #define ARRAY_LENGTH(x) (sizeof (x) / sizeof *(x))
    
    int main(void)
    {
        for (size_t i = 0; i < ARRAY_LENGTH(MyTable); i++) {
            printf("%s\n%#"PRIX32"\n", MyTable[i].theString, MyTable[i].theCRC);
        }
    }
    

    Output from the above program demo:

    First string
    0X7C5B638B
    Second string
    0XC2DE7700
    Third string
    0XE978905F
    Fourth string
    0X7B9353D8
    

    With CMake, the following CMakeFiles.txt will first build the gentables program, then run it to generate the my_table.cdata source, and then build the demo program:

    CMakeFiles.txt

    cmake_minimum_required(VERSION 3.10)
    project(demo)
    
    add_custom_command(
        COMMAND gentable my_table.cdata
        DEPENDS gentable
        OUTPUT my_table.cdata
        COMMENT "Creating string CRC table..."
    )
    
    add_executable(gentable gentable.c)
    target_link_libraries(gentable z)
    
    add_executable(demo demo.c my_table.cdata)
    target_include_directories(demo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
    

    EDIT

    One limitation of the above gentable.c source is that it uses strlen to determine the length of the string, which will not work if there are extra null characters in the string. It is easy to modify it to use the size of the string literal, subtracting 1 to discard the final terminating null. Change the genline function to the following:

    static void genline(FILE *fp, const char *asis, size_t len, const char *binary)
    {
        uint32_t crc = crc32_z(0, (const unsigned char *)binary, len);
    
        fprintf(fp, "{ %s, 0x%"PRIx32" },\n", asis, crc);
    }
    

    And change the X(s) macro to the following:

    #define X(s) genline(fp, #s, sizeof(s) - 1, s);