Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Checking function calls are not passed as arguments (C macros)

In a project I work on we have some utility macros which reference their argument more then once.

Lets use a simple example:

#define ABS(a)  ( (a) < 0 ? (-(a)) : (a) )

Now this is a large code base, and while we do review code, every now and then I find a function call being passed to a macro. This isn't an error, but it means the function call is made multiple times which I normally don't want.

In this case we can replace with fabsf, fabs, abs for float/double/int at least, but let's assume there isn't always a good built-in replacement and the macro will stay a macro.

Example:

f = ABS(dot_v3v3(vel, sp));

/* expands into */
f = ( ( dot_v3v3(vel, sp) ) < 0 ? (-( dot_v3v3(vel, sp) )) : ( dot_v3v3(vel, sp) ) );

So my question is:

Can function calls used inside a macro be detected (either as warnings or errors)?


Partial solution:

Here are some things I already checked on...

Comparing Pointers

This will cause function calls not to compile, but has the drawback that constants like '1' also give errors as well as expressions like (b - c).

#define ABS(a)  ((void)((&a) == (&a)), ( (a) < 0 ? (-(a)) : (a) ))

Note: I found this already quite handy to point out SOME bad macro use, but since it has false-positives, it cant be left in.

C11 Generics

Using _Generic, you can turn C macros into wrappers for inline functions. This means the problem of a function call being invoked multiple times in a macro goes away.

#define ABS(a) \
    _Generic((a), \
        long double: my_abs_double(a), \
        float: my_abs_float(a), \
        int:  my_abs_int(a) \
        /* ... and so on, char, long, short... etc */ \
        )

This isn't a workable solution yet — we still support compilers which don't support generics.

like image 807
ideasman42 Avatar asked Dec 01 '13 01:12

ideasman42


People also ask

Can we pass arguments in macro definition?

Function-like macros can take arguments, just like true functions. To define a macro that uses arguments, you insert parameters between the pair of parentheses in the macro definition that make the macro function-like. The parameters must be valid C identifiers, separated by commas and optionally whitespace.

Can we pass macro to a function in C?

Macros are expanded prior to the code being parsed/compiled as C. You can pass it to a macro function.

Can we call a function inside a macro?

Instead of "trying to put a function inside a macro", simply don't use macros. Write functions. Use functions. Or use macros, but respect their limitations (i.e. they don't work like functions do, even if they look like functions).

When should we prefer macros over inline functions?

On compilers with gcc extensions, macros, unlike inline functions, can test whether their arguments can be evaluated as constants. If one wishes to have a "function" like reverse8bits(x) which computes a bit-reversed byte value, it may be best to use (((x&128)>>7)|((x&64)>>5)...)


1 Answers

I did find this trick in the MirOS C Preprocessor manual in the section on duplicating side effects:

 #define min(X, Y)                \
 ({ typeof (X) x_ = (X);          \
    typeof (Y) y_ = (Y);          \
    (x_ < y_) ? x_ : y_; })
like image 92
Jay Elston Avatar answered Sep 30 '22 08:09

Jay Elston