Danvil
Danvil

Reputation: 23001

Is this function macro safe?

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

Answers (2)

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

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

Related Questions