C and C++ macro details

  • 2020-04-02 01:34:58
  • OfStack

Many C++ books tell us that C macros are the worst of all evils, but things are not as bad as we think, just like goto. Macros do a great job of generating code for us automatically. If a template can generate code of various types for us (type substitution), then a macro can actually generate new code for us on symbols (symbol substitution, symbol addition).

Some syntax problems with macros can be found on Google. Trust me, you don't know as much about macros as you might think. If you don't already know # and # and prescan, you probably don't know enough about macros.

I'm going to talk a little bit about the syntax of macros (macro is only about preprocessor, not semantic analysis) :

Macros can be defined like functions, for example:
#define min(x,y) (x) but in practice, min is expanded as a macro only when min() is written and must be parenthesis, otherwise nothing is done.

2. If the macro needs parameters, you can not pass them, the compiler will give you a warning (macro parameters are not enough), but this will cause an error. As described in the C++ book, the compiler (preprocessor) doesn't do enough syntax checking on macros, so you have to do more checking yourself.

3. A lot of # and ## that programmers don't know
Converts a symbol directly to a string, such as:
# # define STRING (x) x
Const char * STR = STRING(test_string); The content of STR is "test_string, "which means that # will put a double quotation mark directly after the symbol.
The ## symbol connects two symbols to create a new symbol (lexical level), for example:
Define SIGN(x) INT_##x
Int SIGN (1); When expanded, the macro will be: int INT_1;

4. Variable parameter macros, this is cool, it allows you to define similar macros:
# define LOG (format,... ) printf(format, arbitration/arbitration/arbitration)
LOG("%s %d", STR, count);
S = s = s = s = s = s = s = s = s = s = s = s = s = s = s = s = s

5. What happens when a macro calls itself? Such as:
Define TEST(x) (x + TEST(x))
TEST (1); What's going to happen? In order to prevent unbounded recursive expansion, the syntax states that when a macro encounters itself, it stops expansion. That is, when TEST(1) is expanded, another TEST is found during the expansion, and this TEST is treated as a common symbol. TEST (1)
It is finally expanded as: 1 + TEST(1).

6. Prescan for macro parameters , when a macro parameter is put into the macro body, the macro parameter is first expanded in its entirety (with exceptions, see below). When the expanded macro parameters are put into the macro body, the preprocessor scans the newly expanded macro body a second time and continues to expand. Such as:
Define PARAM(x) x
Define ADDPARAM(x) INT_##x
PARAM(ADDPARAM(1));
Since ADDPARAM(1) is a macro parameter for PARAM, expand ADDPARAM(1) to INT_1, and then put INT_1 into PARAM.

The exception is that if the PARAM macro USES # or ## for the macro parameter, the macro parameter is not expanded:
Define PARAM of x
Define ADDPARAM(x) INT_##x
PARAM(ADDPARAM(1)); Will be expanded to "ADDPARAM(1)".

Using this rule, you can create an interesting technique: print out what a macro looks like when it's expanded, so you can easily analyze the code:
#define TO_STRING(x) TO_STRING1(x)
#define TO_STRING1(x) #x
TO_STRING first expands x out (if x is also a macro), then passes TO_STRING1 to convert to a string, now you can:
Const char * STR = TO_STRING(PARAM(ADDPARAM(1))); Go and see what PARAM looks like when it unfolds.

7. An important addition: As I said in the first point, if a macro that looks like a function is used without parenthesis, the preprocessor simply treats the macro as a general notation (that is, no processing).

Let's see how macros can help us generate code automatically. As I said, macros generate code at the symbolic level. When I analyzed the boost.function module, I didn't understand the code because it used so many macros (macros nested, nested). Then I found a small template library called TTL that talked about developing small components to replace some Boost(which is a good reason because Boost is really big). Again, this library contains a function library.

The function here is functor, which I mentioned before. To automatically generate a lot of similar code, the ttl.function library USES a macro:

# define TTL_FUNC_BUILD_FUNCTOR_CALLER/(n)
The template < Typename R, TTL_TPARAMS (n) > /
Struct functor_caller_base # # n/a
/ / /...
The ultimate goal of this macro is to automatically generate many functor_caller_base templates by calling them in a manner similar to TTL_FUNC_BUILD_FUNCTOR_CALLER(1) :
The template struct functor_caller_base1
The template struct functor_caller_base2
The template struct functor_caller_base3
/ / /...
So, the core part is the TTL_TPARAMS(n) macro, and you can see that what this macro ultimately produces is:
Typename T1
Typename T1, typename T2
Typename T1, typename T2, typename T3
/ / /...
Let's analyze the whole process of TTL_TPARAMS(n). Analysis macros mainly grasp some of the points I mentioned above. I suggest you flip through the code for TTL,
Function. HPP, macro_params.hpp, macro_repeat. HPP, macro_misc.hpp, macro_counter.hpp.

So, here we go

Analysis process, layer by layer analysis, layer by layer expansion, such as TTL_TPARAMS(1) :
# define TTL_TPARAMS TTL_TPARAMSX (n) (n, T)
= > TTL_TPARAMSX (1, T)
#define TTL_TPARAMSX(n,t) TTL_REPEAT(n, TTL_TPARAM, TTL_TPARAM_END, t)
= > TTL_REPEAT(1, TTL_TPARAM, TTL_TPARAM_END, T)
# define TTL_TPARAM (n, t) typename t# # n.
# define TTL_TPARAM_END (n, t) typename t# # n
#define TTL_REPEAT(n, m,l,p) TTL_APPEND(TTL_REPEAT_, TTL_DEC(n))(m,l,p) TTL_APPEND(TTL_LAST_REPEAT_,n)(l,p)

Note that TTL_TPARAM and TTL_TPARAM_END, although they are also two macros, are taken as arguments to the TTL_REPEAT macro, and according to the prescan rule, it seems that these macros should be expanded before being passed to TTL_REPEAT. However, as I highlighted earlier, these two macros are function-like macro and need to be bracketed. If they are not bracketed, they are not treated as macros. Therefore, when TTL_REPEAT is expanded, it should be:
= > TTL_APPEND(TTL_REPEAT_, TTL_DEC(1))(TTL_TPARAM,TTL_TPARAM_END,T) TTL_APPEND(TTL_LAST_REPEAT_,1)
TTL_TPARAM_END, T)

This macro body looks very complicated, and can be divided into two parts after careful analysis:
TTL_APPEND(TTL_REPEAT_, TTL_DEC(1))(TTL_TPARAM,TTL_TPARAM_END,T) and
TTL_APPEND (TTL_LAST_REPEAT_, 1) (TTL_TPARAM_END, T)

First, analyze the first part:
#define TTL_APPEND(x,y) TTL_APPEND1(x,y) // expand x,y and then connect x and y
Define TTL_APPEND1(x, y) x ## y
# define TTL_DEC (n) TTL_APPEND (TTL_CNTDEC_, n)

According to the first expand parameter principle, TTL_DEC(1) will be expanded first.
= > TTL_APPEND (TTL_CNTDEC_, 1) = > TTL_CNTDEC_1
Note that TTL_CNTDEC_ is not a macro, TTL_CNTDEC_1 is a macro.
= > Zero, that is, TTL_DEC(1) is eventually expanded to zero. Back to TTL_APPEND:
= > TTL_REPEAT_0 (TTL_TPARAM TTL_TPARAM_END, T)
# define TTL_REPEAT_0 (m, l, p)

TTL_REPEAT_0 is null, so the first part is ignored, leaving only the second part:
TTL_APPEND (TTL_LAST_REPEAT_, 1) (TTL_TPARAM_END, T)
= > TTL_LAST_REPEAT_1 (TTL_TPARAM_END,T) // TTL_APPEND combines TTL_LAST_REPEAT_ with 1
# define TTL_LAST_REPEAT_1 (m, p) m (1, p)
= > TTL_TPARAM_END (1, T)
# define TTL_TPARAM_END (n, t) typename t# # n
= > Typename T1 is expanded.

We figured it out, but it wasn't really what we wanted. We should get the author's programming ideas about macros from those macros. Using macros well may seem like a bit of a sideshow, but they do allow us to code more automatically.


Related articles: