How to write C macro definition to iterate string and modify characters

112 Views Asked by At

I need to make some simple character modifications on compile time, for example instead of:

char text[] = "test text";

I want to be able to write:

char text[] = _MODIFY("test text", 10);

And I want it to be preprocessed as:

char text[] = {116^10,101^10,115^10,116^10,32^10,116^10,101^10,120^10,116^10,0 };

where numbers are ASCII codes XORed with constant. How to define _MODIFY macro preprocessor?

3

There are 3 best solutions below

4
Caulder On

The transformation you were originally describing is exactly, what the compiler does: char text[] = "test text"; is just a shorthand for char text[] = {116, 101, 115, 116, 32, 116, 101, 120, 116, 0 };.

I don't think, that the CPP is capable of doing that on the string definition, because it works string-based not token-based. So it has now clue, what it parameters you give to a macro, it just performs text expansion.

What you can do, is this:

#define LAZY_EVAL () // important space, stop evaluating macro until expansion

#define EXPAND(...) EXPAND_(EXPAND_(EXPAND_(EXPAND_(__VA_ARGS__))))
#define EXPAND_(...) __VA_ARGS__

#define OBFUSCATE_CHAR(C) ((C)^40)

#define OBFUSCATE_STRING(c, ...) OBFUSCATE_CHAR(c),        \
  __VA_OPT__( OBFUSCATE_RECURSIVE LAZY_EVAL (__VA_ARGS__))
#define OBFUSCATE_RECURSIVE() OBFUSCATE_STRING

#define OBFUSCATE(...) { __VA_OPT__(EXPAND(OBFUSCATE_STRING(__VA_ARGS__))) '\0'}

const char text[] = OBFUSCATE('T', 'e', 'x', 't');

This works around the limitations, that macro expansion isn't recursive by deferring evaluation of macro expansion until EXPAND.
Note, that this limits macro expansion to some depth. If you start seeing your macros again, higher the number of EXPAND_ calls.

See also: Recursive macros via __VA_OPT__

Alternatively, you might achieve it with your build system. Either you are modifying the source, or defining a macro with -D... .

0
Klaus On

The C preprocessor simply lacks a feature for doing something like that. If you want that to be done then you have to write your own little preprocessing tool that needs to be executed in a prebuild step for generating the modified source files which are included in your project afterwards.

0
KamilCuk On

this is relatively simple to do once you agree to hard-code the length of the string. The hard requirements is that you have to hard-code the string length, because it is not possible to get it in preprocessor. Having the string length, you call the proper macro and expand the chain however you want.

I would first start with a boilerplate FOREACH macro that calls a function with an index and two arguments. As we know the length at preprocessor time, we can call the proper overload FOREACH_##strlen statically.

For the requirement of hardcoding string length, I would throw in a static assertion, making sure you have to change it when you change the string.

#define FOREACH_0(f, a, b)
#define FOREACH_1(f, a, b)   f(0, a, b),
#define FOREACH_2(f, a, b)   FOREACH_1(f, a, b) f(1, a, b),
#define FOREACH_3(f, a, b)   FOREACH_2(f, a, b) f(2, a, b),
#define FOREACH_4(f, a, b)   FOREACH_3(f, a, b) f(3, a, b),
#define FOREACH_5(f, a, b)   FOREACH_4(f, a, b) f(4, a, b),
#define FOREACH_6(f, a, b)   FOREACH_5(f, a, b) f(5, a, b),
#define FOREACH_7(f, a, b)   FOREACH_6(f, a, b) f(6, a, b),
#define FOREACH_8(f, a, b)   FOREACH_7(f, a, b) f(7, a, b),
#define FOREACH_9(f, a, b)   FOREACH_8(f, a, b) f(8, a, b),
#define FOREACH_10(f, a, b)  FOREACH_9(f, a, b) f(9, a, b),
#define FOREACH_N(_10,_9,_8,_7,_6,_5,_4,_3,_2,_1,...)  FOREACH_##N
#define FOREACH(f, a, b, ...) FOREACH_N(__VA_ARGS__,_10,_9,_8,_7,_6,_5,_4,_3,_2,_1)(f, a, b)
    
#define STATIC_ASSERT(...)  \
    sizeof(struct{_Static_assert(__VA_ARGS__); int dummy;})

#define MODIFY_CB(idx, str, num)  str[idx]^num

#define MODIFY(str, strlen, num)  { \
    FOREACH_##strlen(MODIFY_CB, str, num) \
    0 * STATIC_ASSERT(sizeof(str) == strlen + 1) \
    }

char text[] = MODIFY("test text", 9, 10);

Expanding the code to handle more cases is trivial - it's just adding more FOREACH_*.

Notes: identifiers starting with _ and upper case letter are reserved, you are not allowed to define _MODIFY, do not use such identifiers.