Reputation: 23001
Can you tell me if anything and what can go wrong with this C "function macro"?
#define foo(P,I,X) do { (P)[I] = X; } while(0)
My goal is that foo
behaves exactly like the following function foofunc
for any POD data type T
(i.e. int
, float*
, struct my_struct { int a,b,c; }
):
static inline void foofunc(T* p, size_t i, T x) { p[i] = x; }
For example this is working correctly:
int i = 0;
float p;
foo(&p,i++,42.0f);
It can handle things like &p
due to putting P
in parentheses, it does increment i
exactly once because I
appears only once in the macro and it requires a semicolon at the end of the line due to do {} while(0)
.
Are there other situations of which I am not aware of and in which the macro foo
would not behave like the function foofunc
?
In C++ one could define foofunc as a template and would not need the macro. But I look for a solution which works in plain C (C99).
Upvotes: 3
Views: 451
Reputation: 40625
The do { ... } while(0)
construct protects your result from any harm, your inputs P
and I
are protected by ()
and []
, respectively. What is not protected, is X
. So the question is, whether protection is needed for X
.
Looking at the operator precedence table (http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B#Operator_precedence), we see that only two operators are listed as having lower precedence than =
so that the assignment could steal their argument: the throw
operator (this is C++ only) and the ,
operator.
Now, apart from being C++ only, the throw
operator is uncritical because it does not have a left hand argument that could be stolen.
The ,
operator, on the other hand, would be a problem if X
could contain it as a top level operator. But if you parse the statement
foo(array, index, x += y, y)
you see that the ,
operator would be interpreted to delimit a fourth argument, and
foo(array, index, (x += y, y))
already comes with the parentheses it requires.
To make a long story short:
Yes, your definition is safe.
However, your definition relies on the impossibility to pass stuff, more_stuff
as one macro parameter without adding parentheses. I would prefer not to rely on such intricacies, and just write the obviously safe
#define foo(P, I, X) do { (P)[I] = (X); } while(0)
Upvotes: 2
Reputation: 107769
The fact that your macro works for arbitrary X
arguments hinges on the details of operator precedence. I recommend using parentheses even if they happen not to be necessary here.
#define foo(P,I,X) do { (P)[I] = (X); } while(0)
This is an instruction, not an expression, so it cannot be used everywhere foofunc(P,I,X)
could be. Even if foofunc
returns void
, it can be used in comma expressions; foo
can't. But you can easily define foo
as an expression, with a cast to void
if you don't want to risk using the result.
#define foo(P,I,X) ((void)((P)[I] = (X)))
With a macro instead of a function, all you lose is the error checking. For example, you can write foo(3, ptr, 42)
instead of foo(ptr, 3, 42)
. In an implementation where size_t
is smaller than ptrdiff_t
, using the function may truncate I
, but the macro's behavior is more intuitive. The type of X
may be different from the type that P
points to: an automatic conversion will take place, so in effect it is the type of P
that determines which typed foofunc
is equivalent.
In the important respects, the macro is safe. With appropriate parentheses, if you pass syntactically reasonable arguments, you get a well-formed expansion. Since each parameter is used exactly once, all side effects will take place. The order of evaluation between the parameters is undefined either way.
Upvotes: 2