Search code examples
cc-preprocessor

Why does this .c file #include itself?


Why does this .c file #include itself?

vsimple.c

#define USIZE 8
#include "vsimple.c"
#undef USIZE

#define USIZE 16
#include "vsimple.c"
#undef USIZE

#define USIZE 32
#include "vsimple.c"
#undef USIZE

#define USIZE 64
#include "vsimple.c"
#undef USIZE

Solution

  • The file includes itself so the same source code can be used to generate 4 different sets of functions for specific values of the macro USIZE.

    The #include directives are actually enclosed in an #ifndef, which limits the recursion to a single level:

    #ifndef USIZE
    
    // common definitions
    ...
    //
    
    #define VSENC vsenc
    #define VSDEC vsdec
    
    #define USIZE 8
    #include "vsimple.c"
    #undef USIZE
    
    #define USIZE 16
    #include "vsimple.c"
    #undef USIZE
    
    #define USIZE 32
    #include "vsimple.c"
    #undef USIZE
    
    #define USIZE 64
    #include "vsimple.c"
    #undef USIZE
    
    #else // defined(USIZE)
    
    // macro expanded size specific functions using token pasting
    
    ...
    
    #define uint_t TEMPLATE3(uint, USIZE, _t)
    
    unsigned char *TEMPLATE2(VSENC, USIZE)(uint_t *__restrict in, size_t n, unsigned char *__restrict out) {
       ...
    }
    
    unsigned char *TEMPLATE2(VSDEC, USIZE)(unsigned char *__restrict ip, size_t n, uint_t *__restrict op) {
       ...
    }
    
    #endif
    

    The functions defined in this module are

    // vsencNN: compress array with n unsigned (NN bits in[n]) values to the buffer out. Return value = end of compressed output buffer out
    unsigned char *vsenc8( unsigned char  *__restrict in, size_t n, unsigned char  *__restrict out);
    unsigned char *vsenc16(unsigned short *__restrict in, size_t n, unsigned char  *__restrict out);
    unsigned char *vsenc32(unsigned       *__restrict in, size_t n, unsigned char  *__restrict out);
    unsigned char *vsenc64(uint64_t       *__restrict in, size_t n, unsigned char  *__restrict out);
    
    // vsdecNN: decompress buffer into an array of n unsigned values. Return value = end of compressed input buffer in
    unsigned char *vsdec8( unsigned char  *__restrict in, size_t n, unsigned char  *__restrict out);
    unsigned char *vsdec16(unsigned char  *__restrict in, size_t n, unsigned short *__restrict out);
    unsigned char *vsdec32(unsigned char  *__restrict in, size_t n, unsigned       *__restrict out);
    unsigned char *vsdec64(unsigned char  *__restrict in, size_t n, uint64_t       *__restrict out);
    

    They are all expanded from the two function definitions in vsimple.c:

    unsigned char *TEMPLATE2(VSENC, USIZE)(uint_t *__restrict in, size_t n, unsigned char *__restrict out) {
       ...
    }
    
    unsigned char *TEMPLATE2(VSDEC, USIZE)(unsigned char *__restrict ip, size_t n, uint_t *__restrict op) {
       ...
    }
    

    The TEMPLATE2 and TEMPLATE3 macros are defined in conf.h as

    #define TEMPLATE2_(_x_, _y_) _x_##_y_
    #define TEMPLATE2(_x_, _y_) TEMPLATE2_(_x_,_y_)
    
    #define TEMPLATE3_(_x_,_y_,_z_) _x_##_y_##_z_
    #define TEMPLATE3(_x_,_y_,_z_) TEMPLATE3_(_x_, _y_, _z_)
    

    These macros are classic preprocessor constructions to create identifiers via token pasting. TEMPLATE2 and TEMPLATE2_ are more commonly called GLUE and XGLUE.

    The function template starts as:

    unsigned char *TEMPLATE2(VSENC, USIZE)(uint_t *__restrict in, size_t n, unsigned char *__restrict out) ...
    

    It is expanded in the first recursive inclusion with USIZE defined as 8 into:

    unsigned char *vsenc8(uint8_t *__restrict in, size_t n, unsigned char *__restrict out) ...
    

    The second recursive inclusion, with USIZE defined as 16, expands the template as:

    unsigned char *vsenc16(uint16_t *__restrict in, size_t n, unsigned char *__restrict out) ...
    

    and 2 more inclusions define vsenc32 and vsenc64.

    This usage of preprocessed source code is more common with separate files: one for the instantiating part that has all the common definitions, especially the macros, and a separate file for the code and data templates, which is included multiple times with different macro definitions.

    A good example is the generation of enums, string and structures arrays from atom and opcode definitions in QuickJS.