1

I see below code snippet in fwts code base:

#define FWTS_CONCAT(a, b) a ## b
#define FWTS_CONCAT_EXPAND(a,b) FWTS_CONCAT(a, b)
#define FWTS_ASSERT(e, m)   \
enum { FWTS_CONCAT_EXPAND(FWTS_ASSERT_ ## m ## _in_line_, __LINE__) = 1 / !!(e) }

#define FWTS_REGISTER_FEATURES(name, ops, priority, flags, features)    \
/* Ensure name is not too long */                   \
FWTS_ASSERT(FWTS_ARRAY_LEN(name) < 16,                  \
    fwts_register_name_too_long);   

My questions are:

  • For the definition of FWTS_ASSERT(e, m), I know the !! can convert whatever value into 1 or 0. But doesn't it cause error for FWTS_ASSERT() when !!(e) evaluates to 0 thus leads to 1/0 ?

  • And btw, the FWTS_CONCAT_EXPAND(a,b) and FWTS_CONCAT(a, b) seem to be duplicated, why do we need 2 of them?

ADD 1

Based on @Klas Lindbäck's answer, I want to go through the macro expansion with a concrete example.

Suppose I have:

#define M_1  abc
#define M_2  123

Then I guess the expansion process of FWTS_CONCAT_EXPAND(M_1,M_2) should be:

FWTS_CONCAT_EXPAND(M_1,M_2)
->
FWTS_CONCAT(abc, 123)
->
abc123

If I directly applying FWTS_CONCAT(M_1, M_2), will it be expanded like this?

FWTS_CONCAT(M_1, M_2)
->
M_1M_2
-> 
Bang! M_1M_2 is an invalid symbol!

(Please correct me if I am wrong...)

ADD 2

Tried with gcc -E macroTest.c -o macroTest.i:

(macroTest.c)

#define M_1  abc
#define M_2  123
#define FWTS_CONCAT(a, b) a ## b
#define FWTS_CONCAT_EXPAND(a,b) FWTS_CONCAT(a, b)

FWTS_CONCAT_EXPAND(M_1, M_2)
FWTS_CONCAT(M_1,M_2)

(macroTest.i)

# 1 "macroTest.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "macroTest.c"

abc123
M_1M_2

I think I get the point of the macro expansion rule. Below are some related concepts and quotation:

Argument Prescan:

Macro arguments are completely macro-expanded before they are substituted into a macro body, unless they are stringified or pasted with other tokens. After substitution, the entire macro body, including the substituted arguments, is scanned again for macros to be expanded. The result is that the arguments are scanned twice to expand macro calls in them.

Stringification

When a macro parameter is used with a leading ‘#’, the preprocessor replaces it with the literal text of the actual argument, converted to a string constant.

Token Pasting / Token Concatenation:

It is often useful to merge two tokens into one while expanding macros. This is called token pasting or token concatenation. The ‘##’ preprocessing operator performs token pasting. When a macro is expanded, the two tokens on either side of each ‘##’ operator are combined into a single token, which then replaces the ‘##’ and the two original tokens in the macro expansion.

So the detailed process of my scenario is like this:

FWTS_CONCAT_EXPAND(M_1, M_2)   
-> FWTS_CONCAT_EXPAND(abc, 123) // M_1, M_2 pre-expanded since FWTS_CONCAT_EXPAND has no ##.
-> FWTS_CONCAT(abc, 123) // FWTS_CONCAT_EXPAND expanded into FWTS_CONCAT
-> abc123  // FWTS_CONCAT expanded

FWTS_CONCAT(M_1,M_2)
-> M_1M_2 //M_1, M_2 are not pre-expanded because of the ## in FWTS_CONCAT
-> DEADEND
5
  • I think it's supposed to cause an error. Commented Sep 6, 2016 at 6:40
  • It most likely is supposed to cause a compilation error, since the constant 1/0 can't be computed. Commented Sep 6, 2016 at 6:40
  • Thanks. But I just don't understand why define a enum type to raise compile-time error? Anything special about enum? Commented Sep 6, 2016 at 6:42
  • @smwikipedia: Other than its values having to be constant (And having a single line error output, giving the identifier's complaint)? I've seen similar done with arrays sizes, but this wouldn't (even accidentally) allocate any storage for the item. Commented Sep 6, 2016 at 6:47
  • 3
    enum is nothing special in this; I have used typedef arrays with similar trick. Point is that we perform compile time check with C constructs, when preprocessor won't work. For example, you cannot use sizeof in #if statement to test array size. Commented Sep 6, 2016 at 7:19

1 Answer 1

3

The macros are used for compile time checking. This is useful when you write code that will be compiled and run on many different platforms and where some platforms may not be compatible.

If the first parameter to FWTS_ASSERT evaluates to non-zero (true) then !!(e) will evaluate to 1 and the enum will be created with the name FWTS_ASSERT_<second parameter>_in_line_<line>. I suspect that the enum is never actually used.

If the first parameter to FWTS_ASSERT evaluates to 0 (= false) then the compiler will try to compute 1/0 and generate a compiler error where it will hopefully tell which enum member caused the error, in this case FWTS_ASSERT_fwts_register_name_to_long_in_line_4.

And btw, the FWTS_CONCAT_EXPAND(a,b) and FWTS_CONCAT(a, b) seem to be duplicated, why do we need 2 of them?

FTW_CONCAT_EXPAND is done in 2 steps because we want to first expand any macros in the parameters and then perform the concatenation. Doing it in two steps makes the preprocessor do macro expansion of the parameters before it does the string concatenation.

Sign up to request clarification or add additional context in comments.

1 Comment

@smwikipedia A good way to find out would be to try it yourself! ;-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.