[comp.lang.c] macros w/o side effects

bernsten@phoenix.Princeton.EDU (Dan Bernstein) (05/15/89)

In article <2337@mit-caf.MIT.EDU> vlcek@mit-caf.UUCP (Jim Vlcek) writes:
> In article <181@larry.sal.wisc.edu> jwp@larry.sal.wisc.edu.UUCP
> (Jeffrey W Percival) writes: 
  [if you rely upon .h files for function declarations,
   it is easy to later make a function into a macro]
> >Is that interpretation unflawed?
> If what was once a function is turned into a macro, programs which
> passed arguments with side effects to the function may not work
> correctly with the macro.  You know the tune:
> #define square(x) ((x)*(x))  /* Used to be a function ... needed speed */
> y = square(++i);
> Any macro->function sleight of hand had best not take place behind the
> scenes, and in fact should probably not take place at all.  Going the
> other direction is probably considered safe, although some programs
> may have depended upon the side effects of the macro which is now a
> function.  Probably anyone who does so deserves to suffer, though...

Oh dear. I should be burned at the stake for some of the macros I write.
I even take functions like

  sqaure(in,out) int in; int *out; { *out = in * in; }

and convert them into macros like

  #define square(in,out) { int Tsquare1 = in; int *Tsquare2 = out;
			   *Tsquare2 = Tsquare1 * Tsquare1; }

Gasp! Where's the error when I do square(i++,&result)? Where have the
side effects gone? How come this macro does exactly the same thing as
the function except that it's faster? And I would have thought from the
above article that this was impossible!

I have a tiny shell script that does the above conversion automatically;
I suppose it's being inefficient when a variable is only used once (as
in the above use of Tsquare2), but it basically does the job right.
There are no side effects, it's all perfectly safe, and I don't think
I deserve to suffer for it.

Since C is not fully expression-based, it is impossible to do the above
conversion for functions that you're using as functions rather than as
procedures (i.e., functions that return a type and may not take up an
entire statement). One can to some extent use the comma operator, but
since one can't really declare local variables without { }, the results
can't be nested. However, any procedural function can be quite safely
converted into a macro.

What we really want here is a feature often observed to be missing from C:
inline functions. Inline functions need not be syntactically different
from normal functions; perhaps we could add an ``inline'' keyword to a
function declaration, with the restriction that the function must be
defined there (in the header file). A different approach would be to
make C fully expression-based, so that the comma operator would disappear
in favor of semicolons; the idea is to allow all possible statement
constructs as expression constructs. Of course, this must include local
variables.

---Dan Bernstein, bernsten@phoenix.princeton.edu

vlcek@mit-caf.MIT.EDU (Jim Vlcek) (05/15/89)

A short while ago, Jeffrey Percival (in <181@larry.sal.wisc.edu>,
noted that if one relies upon header files for function declarations,
then one might later seamlessly change the function(s) into macros.
He asked if this interpretation was unflawed.

I pointed out, in <2337@mit-caf>, that the macros might have problems
with arguments with side effects - the old ``square(++i)'' thing.  I
made a mistake in there, by the way.  I said:

``Any macro->function sleight of hand had best not take place behind
  the scenes, and in fact should probably not take place at all.
  Going the other direction is probably considered safe, although some
  programs may have depended upon the side effects of the macro which
  is now a function.  Probably anyone who does so deserves to suffer,
  though...''

That first line should read ``Any function->macro sleight of hand...''
Hopefully, this was clear already from the context.

Which brings us to Dan Bernstein, in <8420@phoenix.princeton.edu>:

``Oh dear. I should be burned at the stake for some of the macros I write.
  I even take functions like

  sqaure(in,out) int in; int *out; { *out = in * in; }

  and convert them into macros like

  #define square(in,out) { int Tsquare1 = in; int *Tsquare2 = out;
			   *Tsquare2 = Tsquare1 * Tsquare1; }

``Gasp! Where's the error when I do square(i++,&result)? Where have the
  side effects gone? How come this macro does exactly the same thing as
  the function except that it's faster? And I would have thought from the
  above article that this was impossible!''

OK, try this:

  if (p>0)
    square(p,&q);
  else
    q=0;

It'll work if square() is a function, but fail if square() is a macro
as you've defined.  Can you find the syntax error?  Further, as Dan
points out later, this is limited to functions or macros which do not
return a value, and hence is only a special case.  It's for these
reasons that I never define any macros as compound statements.

Back to Dan:

``I have a tiny shell script that does the above conversion
  automatically; I suppose it's being inefficient when a variable is
  only used once (as in the above use of Tsquare2), but it basically
  does the job right. There are no side effects, it's all perfectly
  safe, and I don't think I deserve to suffer for it.''

My point about suffering was perhaps somewhat obscure.  I said that it
was probably OK to change a macro into a function, although the
behavior could not be guaranteed to be identical in that the function
would evaluate its arguments only once, whereas the macro would
evaluate each argument as many times as it appeared.  My point was
that anyone who depended upon the side effects ensuing from multiple
evaluations of a macro argument probably deserves to suffer.  Perhaps
someone can show a reasonable counterexample, however.

Jim Vlcek (vlcek@caf.mit.edu  uunet!mit-caf!vlcek)

laba-3hd@tarantula.berkeley.edu (Sam Shen) (05/16/89)

In article <8420@phoenix.Princeton.EDU> bernsten@phoenix.Princeton.EDU
(Dan Bernstein) writes:

   Since C is not fully expression-based, it is impossible to do the
   above conversion for functions that you're using as functions
   rather than as procedures (i.e., functions that return a type and
   may not take up an entire statement). One can to some extent use
   the comma operator, but since one can't really declare local
   variables without { }, the results can't be nested. However, any
   procedural function can be quite safely converted into a macro.

GNU C allows this.  From the gcc manual:

A compound statement in parentheses may appear inside an expression in GNU
C.  This allows you to declare variables within an expression.  For
example:

({ int y = foo (); int z;
   if (y > 0) z = y;
   else z = - y;
   z; })

is a valid (though slightly more complex than necessary) expression
for the absolute value of foo().

The manual goes on to note that with the typedef extension, we can
do really neat things like:

#define max(a,b) \
  ({typedef _ta = (a), _tb = (b);  \
    _ta _a = (a); _tb _b = (b);    \
    _a > _b ? _a : _b; })

Anyhow Dan Bernstein dreams on...

   What we really want here is a feature often observed to be missing from C:
   inline functions. Inline functions need not be syntactically
   different from normal functions; perhaps we could add an ``inline''
   keyword to a function declaration, with the restriction that the
   function must be defined there (in the header file). A different
   approach would be to make C fully expression-based, so that the
   comma operator would disappear in favor of semicolons; the idea is
   to allow all possible statement constructs as expression
   constructs. Of course, this must include local variables.

   ---Dan Bernstein, bernsten@phoenix.princeton.edu

GNU C has this too.  We could say

inline int max(int a, int b)
{
	return a > b ? a : b;
}

But then we would have the automatic typing of the macro above.

	-Sam Shen
	laba-3hd@web.berkeley.edu
--
----------
Sam Shen				resident of 260 Evans Hall, UCB, 94720.
...!ucbvax!web!laba-3hd		| "Say yur prayers, yuh flea-bitten varmit!"
laba-3hd@web.berkeley.edu	| 		--Yosemite Sam

tps@chem.ucsd.edu (Tom Stockfisch) (05/16/89)

In article <8420@phoenix.Princeton.EDU> bernsten@phoenix.Princeton.EDU (Dan Bernstein) writes:
#In article <2337@mit-caf.MIT.EDU> vlcek@mit-caf.UUCP (Jim Vlcek) writes:
## In article <181@larry.sal.wisc.edu> jwp@larry.sal.wisc.edu.UUCP
##   You know the tune:
## #define square(x) ((x)*(x))  /* Used to be a function ... needed speed */
## y = square(++i);
## Any macro->function sleight of hand had best not take place behind the
## scenes, and in fact should probably not take place at all....

#I even take functions like
#
#  sqaure(in,out) int in; int *out; { *out = in * in; }
#
#and convert them into macros like
#
#  #define square(in,out) { int Tsquare1 = in; int *Tsquare2 = out;
#			   *Tsquare2 = Tsquare1 * Tsquare1; }
#
#Gasp! Where's the error when I do square(i++,&result)? Where have the
#side effects gone? How come this macro does exactly the same thing as
#the function except that it's faster? And I would have thought from the
#above article that this was impossible!
#There are no side effects, it's all perfectly safe, and I don't think
#I deserve to suffer for it.



Well, it's not *perfectly* safe.  What if the poor user who includes
square.h does

	# include "square.h"

	...
	# define Tsquare1	foo; bar
	...
	square(i++,&result);

But I agree, it's safe enough to make it worth the remaining risk.
And it would be nice to have C++'s inline facility.
-- 

|| Tom Stockfisch, UCSD Chemistry	tps@chem.ucsd.edu

ned@pebbles.cad.mcc.com (CME Ned Nowotny) (06/20/89)

In article <8420@phoenix.Princeton.EDU> bernsten@phoenix.Princeton.EDU (Dan Bernstein) writes:
=>In article <2337@mit-caf.MIT.EDU> vlcek@mit-caf.UUCP (Jim Vlcek) writes:
=>> In article <181@larry.sal.wisc.edu> jwp@larry.sal.wisc.edu.UUCP
=>> (Jeffrey W Percival) writes: 
=>  [if you rely upon .h files for function declarations,
=>   it is easy to later make a function into a macro]
=>> >Is that interpretation unflawed?
=>> If what was once a function is turned into a macro, programs which
=>> passed arguments with side effects to the function may not work
=>> correctly with the macro.  You know the tune:
=>> #define square(x) ((x)*(x))  /* Used to be a function ... needed speed */
=>> y = square(++i);
=>> Any macro->function sleight of hand had best not take place behind the
=>> scenes, and in fact should probably not take place at all.  Going the
=>> other direction is probably considered safe, although some programs
=>> may have depended upon the side effects of the macro which is now a
=>> function.  Probably anyone who does so deserves to suffer, though...
=>
=>Oh dear. I should be burned at the stake for some of the macros I write.
=>I even take functions like
=>
=>  sqaure(in,out) int in; int *out; { *out = in * in; }
=>
=>and convert them into macros like
=>
=>  #define square(in,out) { int Tsquare1 = in; int *Tsquare2 = out;
=>			   *Tsquare2 = Tsquare1 * Tsquare1; }
=>
=>Gasp! Where's the error when I do square(i++,&result)? Where have the
=>side effects gone? How come this macro does exactly the same thing as
=>the function except that it's faster? And I would have thought from the
=>above article that this was impossible!
=>

However, it is not much fun to debug code with something like the following
call to square():

	square( Tsquare1, &Tsquare2 );

Of course, the author would never use Tsquare[12] as variable names in
code that calls square.  However, other users of his header file might.
There are variable name constructions which would be extremely unlikely
to cause problems, but none guaranteed safe.

Ned Nowotny, MCC CAD Program, Box 200195, Austin, TX  78720  Ph: (512) 338-3715
ARPA: ned@mcc.com                   UUCP: ...!cs.utexas.edu!milano!cadillac!ned
-------------------------------------------------------------------------------
"We have ways to make you scream." - Intel advertisement in the June 1989 DDJ.