Search code examples
cstructmacrosprintf

How to create a variable number of printf formats and values in a C macro?


I have a C struct, for which I've written printf formatting and value macros:

#define FREQ_MAX_SYMBOL 8

struct freq {
    int num_symbols; // <= FREQ_MAX_SYMBOL
    int tot_freq;    // sum of all the frequencies
    int req_freqs[FREQ_MAX_SYMBOL]; // required frequencies
    int cur_freqs[FREQ_MAX_SYMBOL]; // current frequencies
};

#define FREQ_FORMAT "{num_symbols: %i, tot_freq: %i, req_freqs: [%i,%i,%i,%i,%i,%i.%i,%i], cur_freqs: [%i,%i,%i,%i,%i,%i,%i,%i]}"

#define FREQ_VALUES(f) f->num_symbols, f->tot_freq, \
f->req_freqs[0], f->req_freqs[1], f->req_freqs[2], f->req_freqs[3], f->req_freqs[4], f->req_freqs[5], f->req_freqs[6], f->req_freqs[7], \
f->cur_freqs[0], f->cur_freqs[1], f->cur_freqs[2], f->cur_freqs[3], f->cur_freqs[4], f->cur_freqs[5], f->cur_freqs[6], f->cur_freqs[7]     

Is it possible to alter the macros so that they only create a num_symbols number of formats and values for req_freqs[] and cur_freqs[]?

e.g. If num_symbols == 2 only req_freqs[0] and [1] and cur_freqs[0] and [1] should be printed.


Solution

  • This is likely not a good idea to begin with and you'd be much better off using a function + loops in run-time. Especially since you already threw performance out the window by using printf, so there aren't many arguments for doing all of this at compile-time.

    That being said, if you insist on macros and if you can guarantee that the num_symbols member corresponds to FREQ_MAX_SYMBOL, you could cook up a clunky, evil list of macros such as these:

    #define FREQ_FORMAT_1 "{num_symbols: %i, tot_freq: %i, req_freqs: [%i], cur_freqs: [%i]}"
    #define FREQ_FORMAT_2 "{num_symbols: %i, tot_freq: %i, req_freqs: [%i,%i], cur_freqs: [%i,%i]}"
    #define FREQ_FORMAT_3 "{num_symbols: %i, tot_freq: %i, req_freqs: [%i,%i,%i], cur_freqs: [%i,%i,%i]}"
    
    #define FREQ_PARAM_1 (f).req_freqs[0], \
                         (f).cur_freqs[0]
    #define FREQ_PARAM_2 (f).req_freqs[0], (f).req_freqs[1], \
                         (f).cur_freqs[0], (f).cur_freqs[1]
    #define FREQ_PARAM_3 (f).req_freqs[0], (f).req_freqs[1], (f).req_freqs[2], \
                         (f).cur_freqs[0], (f).cur_freqs[1], (f).cur_freqs[2]
    

    You'll need an upper limit for how many arguments you support. You'd then call these like:

    #define CONCAT_(a,b) a##b
    #define CONCAT(a,b) CONCAT_(a,b)
    
    #define FREQ_FORMAT CONCAT(FREQ_FORMAT_, FREQ_MAX_SYMBOL)
    #define FREQ_VALUES(f) (f).num_symbols, (f).tot_freq, CONCAT(FREQ_PARAM_, FREQ_MAX_SYMBOL)
    

    At least the first macro FREQ_FORMAT requires an extra level of macro expansion since it is an object-like macro and not a function-like macro. Hence we need a helper macro to expand that one. And then the usual problem with ## getting applied before further macro expansion, so that one needs to be in a helper macro of its own.

    Full example:

    #include <stdio.h>
    
    #define FREQ_MAX_SYMBOL 3
    
    struct freq {
        int num_symbols; // <= FREQ_MAX_SYMBOL
        int tot_freq;    // sum of all the frequencies
        int req_freqs[FREQ_MAX_SYMBOL]; // required frequencies
        int cur_freqs[FREQ_MAX_SYMBOL]; // current frequencies
    };
    
    #define FREQ_FORMAT_1 "{num_symbols: %i, tot_freq: %i, req_freqs: [%i], cur_freqs: [%i]}"
    #define FREQ_FORMAT_2 "{num_symbols: %i, tot_freq: %i, req_freqs: [%i,%i], cur_freqs: [%i,%i]}"
    #define FREQ_FORMAT_3 "{num_symbols: %i, tot_freq: %i, req_freqs: [%i,%i,%i], cur_freqs: [%i,%i,%i]}"
    
    #define FREQ_PARAM_1 (f).req_freqs[0], \
                         (f).cur_freqs[0]
    #define FREQ_PARAM_2 (f).req_freqs[0], (f).req_freqs[1], \
                         (f).cur_freqs[0], (f).cur_freqs[1]
    #define FREQ_PARAM_3 (f).req_freqs[0], (f).req_freqs[1], (f).req_freqs[2], \
                         (f).cur_freqs[0], (f).cur_freqs[1], (f).cur_freqs[2]
    
    #define CONCAT_(a,b) a##b
    #define CONCAT(a,b) CONCAT_(a,b)
    
    #define FREQ_FORMAT CONCAT(FREQ_FORMAT_, FREQ_MAX_SYMBOL)
    #define FREQ_VALUES(f) (f).num_symbols, (f).tot_freq, CONCAT(FREQ_PARAM_, FREQ_MAX_SYMBOL)
    
    int main() 
    {
       struct freq f = 
       { 
         .num_symbols = FREQ_MAX_SYMBOL, 
         .tot_freq    = 123, 
         .req_freqs   = {1,2,3}, 
         .cur_freqs   = {4,5,6},
       };
    
      printf(FREQ_FORMAT, FREQ_VALUES(f));
    }
    

    Output:

    {num_symbols: 3, tot_freq: 123, req_freqs: [1,2,3], cur_freqs: [4,5,6]}