Help with function-like macros

S

Stephen Sprunk

On a project I'm working on, I ran across the following macros:

/* assume s is struct stream *, s->p is char, v is unit16_t or uint32_t */
#define in_uint16_le(s,v) { v = *((s)->p++); v += *((s)->p++) << 8; }
#define in_uint32_le(s,v) { in_uint16_le(s,v) \
v += *((s)->p++) << 16; v += *((s)->p++) << 24; }

I'm personally not fond of function-like macros and wanted to turn these
into static inline functions, but I'm having trouble doing so because the
above macros modify their second argument -- one of the reasons I dislike
them in the first place. This seems to require that the signatures be
changed to:

static inline uint16_t in_uint16_le(struct stream *s);
static inline uint32_t in_uint32_le(struct stream *s);

However, I'm not comfortable both swapping out the macros _and_ rewriting
hundreds of calls to each at the same time, so I'd like some function-like
macros with the new signature as a transition step. I'm also concerned that
there may be compilers out there that puke on "static inline" or don't
optimize it properly, so I'd only use them on platforms where it works
equally well or better.

However, I can't figure out exactly how to write the new macros; can someone
please send reworked versions?

S
 
M

Michael Mair

Stephen said:
On a project I'm working on, I ran across the following macros:

/* assume s is struct stream *, s->p is char, v is unit16_t or uint32_t */
#define in_uint16_le(s,v) { v = *((s)->p++); v += *((s)->p++) << 8; }
#define in_uint32_le(s,v) { in_uint16_le(s,v) \
v += *((s)->p++) << 16; v += *((s)->p++) << 24; }

I would rather use the do....while (0) form (two threads above this
one).
s->p seems to be of type char * instead of char, as indicated above.
If this is true: Consider the use of unsigned char if you can.
I'm personally not fond of function-like macros and wanted to turn these
into static inline functions, but I'm having trouble doing so because the
above macros modify their second argument -- one of the reasons I dislike
them in the first place. This seems to require that the signatures be
changed to:

static inline uint16_t in_uint16_le(struct stream *s);
static inline uint32_t in_uint32_le(struct stream *s);

Why not stay with the "interface" given by the macro?

static inline void in_uint16_le(struct stream *s, uint16_t *v);
static inline void in_uint32_le(struct stream *s, uint32_t *v);

Replacing in_uintN_t(s,v) by in_uintN_t(s,&(v)) is easier.

Notes: You did not comment on the change of the value of p,
so I guess that this is intended.
Note also that uint16_t is not guaranteed by the C99 standard
whereas uint_least16_t and uint_fast16_t are (same for 8, 32, 64 and
other numbers of bits).

However, I'm not comfortable both swapping out the macros _and_ rewriting
hundreds of calls to each at the same time, so I'd like some function-like
macros with the new signature as a transition step. I'm also concerned that
there may be compilers out there that puke on "static inline" or don't
optimize it properly, so I'd only use them on platforms where it works
equally well or better.

However, I can't figure out exactly how to write the new macros; can someone
please send reworked versions?

The ugly thing is that you have to work with the comma operator.
Consider

#include <stdio.h>

#define get16(p) ((p)+=2, *((p)-2)+(*((p)-1)<<8))

int main (void)
{
unsigned char *ptr, arr[]={255,255};

ptr = arr;

printf("%u\n",get16(ptr));

return 0;
}


Cheers
Michael
 
M

Michael Mair

Michael said:
I would rather use the do....while (0) form (two threads above this
one).
s->p seems to be of type char * instead of char, as indicated above.
If this is true: Consider the use of unsigned char if you can.



Why not stay with the "interface" given by the macro?

static inline void in_uint16_le(struct stream *s, uint16_t *v);
static inline void in_uint32_le(struct stream *s, uint32_t *v);

Replacing in_uintN_t(s,v) by in_uintN_t(s,&(v)) is easier.

Notes: You did not comment on the change of the value of p,
so I guess that this is intended.
Note also that uint16_t is not guaranteed by the C99 standard
whereas uint_least16_t and uint_fast16_t are (same for 8, 32, 64 and
other numbers of bits).




The ugly thing is that you have to work with the comma operator.
Consider

#include <stdio.h>

#define get16(p) ((p)+=2, *((p)-2)+(*((p)-1)<<8))

Just thought about it; for the record:
"& 0xFF" makes sense for CHAR_BIT > 8, so for portable code, you may
want to do it like this:

#define get8n(p) (*(p) & 0xFF)
#define get16(p) ((p)+=2, get8n((p)-2) + get8n((p)-1)<<8)

(untested, "n" stands for non-modifying)
int main (void)
{
unsigned char *ptr, arr[]={255,255};

ptr = arr;

printf("%u\n",get16(ptr));

return 0;
}


Cheers
Michael
 
M

Mark L Pappin

Michael Mair said:
Stephen Sprunk wrote: ....

One (horribly perverted) way to emulate pass-by-reference is to do
what the author of GMP 2.0[0] did - use an array of length 1 as your
type (in that case it was done via typedef; you could do the same).

This way the calls can stay the same, but you will have to modify the
type of the objects used as second-arguments (although the compiler
will help you by pointing out any you've forgotten).

e.g.

/* struct used so we actually have new types, not just names */
typedef struct { uint16_t u; } foo16_t[1];
typedef struct { uint32_t u; } foo32_t[1];

static void in_uint16_le(struct stream *s, foo16_t v)
{
v[0].u = *(s->p++);
v[0].u += *(s->p++) << 8;
}
static void in_uint32_le(struct stream *s, foo32_t v)
{
v[0].u = *(s->p++);
v[0].u += *(s->p++) << 8;
v[0].u += *(s->p++) << 16;
v[0].u += *(s->p++) << 24;
}

There may also be some point in replacing
*(s->p++) with (*(s->p++)&0xff)
and replacing
+= with |=
in the above.
Replacing in_uintN_t(s,v) by in_uintN_t(s,&(v)) is easier.

This approach doesn't even require that.

Not that I'm recommending it, either, because pass-by-reference
Doesn't Happen in C functions, but since your code already does it
with macros-that-look-like-functions (and aren't named in UPPER CASE
to make their nature obvious) this avoids one extra class of pain.

mlp

[0] GNU Multiple Precision arithmetic library
Version 1 didn't mess with the programmer's mind so much; the
"array of 1" hack was introduced with 2.0
 
T

Tim Rentsch

Stephen Sprunk said:
On a project I'm working on, I ran across the following macros:

/* assume s is struct stream *, s->p is char, v is unit16_t or uint32_t */

#define in_uint16_le(s,v) { v = *((s)->p++); v += *((s)->p++) << 8; }
#define in_uint32_le(s,v) { in_uint16_le(s,v) \
v += *((s)->p++) << 16; v += *((s)->p++) << 24; }

First suggestion: these macros can be written in expression form
rather than statement form, and it's probably a good idea to do
that:

#define in_uint16_le(s,v) (v = *((s)->p++), v += *((s)->p++) << 8)
/* etc */

I mention this because this transformation should aid in converting
to a more function-call-like semantics.

I'm personally not fond of function-like macros and wanted to turn these
into static inline functions, but I'm having trouble doing so because the
above macros modify their second argument -- one of the reasons I dislike
them in the first place. This seems to require that the signatures be
changed to:

static inline uint16_t in_uint16_le(struct stream *s);
static inline uint32_t in_uint32_le(struct stream *s);

However, I'm not comfortable both swapping out the macros _and_ rewriting
hundreds of calls to each at the same time, so I'd like some function-like
macros with the new signature as a transition step. I'm also concerned that
there may be compilers out there that puke on "static inline" or don't
optimize it properly, so I'd only use them on platforms where it works
equally well or better.

However, I can't figure out exactly how to write the new macros; can someone
please send reworked versions?

Here is a sketch of an approach. First step:

#define in_uint16_le(s,v) in_puint16_le(s,&(v))
#define in_uint32_le(s,v) in_puint32_le(s,&(v))

#define in_puint16_le(s,v) (*(v) = *((s)->p++), *(v) += *((s)->p++) << 8 )
#define in_puint32_le(s,v) \
(*(v) = *((s)->p++), *(v) += *((s)->p++) << 8, \
*(v) += *((s)->p++) << 16, *(v) += *((s)->p++) << 24)

The first step transitions to macros that use an address, but without
needing to change any of the macro calls (not counting the changes
needed after switching to the expression form, which should be only
adding ;'s in places).


Second step - allow functions in place of macros:

#define in_uint16_le(s,v) in_puint16_le(s,&(v))
#define in_uint32_le(s,v) in_puint32_le(s,&(v))

#if EXPAND_in_puint_X_le_AS_CALLS
# define in_puint16_le(s,v) C_in_puint16_le(s,v)
# define in_puint32_le(s,v) C_in_puint32_le(s,v)
#else
# define in_puint16_le(s,v) CPP_in_puint16_le(s,v)
# define in_puint32_le(s,v) CPP_in_puint32_le(s,v)
#endif

#define CPP_in_puint16_le(s,v) \
(*(v) = *((s)->p++), *(v) += *((s)->p++) << 8 )
#define CPP_in_puint32_le(s,v) \
(*(v) = *((s)->p++), *(v) += *((s)->p++) << 8, \
*(v) += *((s)->p++) << 16, *(v) += *((s)->p++) << 24)

static uint16 inline
C_in_puint16_le( struct stream *s, uint16 *v ){
return CPP_in_puint16_le( s, v );
}

static uint32 inline
C_in_puint32_le( struct stream *s, uint32 *v ){
return CPP_in_puint32_le( s, v );
}

By #define'ing EXPAND_in_puint_X_le_AS_CALLS as 0 or 1,
either CPP expansion behavior or function call behavior
can be generated.

Note 1: to get void results rather than uint16/uint32 results, put
casts in the bottom-most macros, and change the function definitions
appropriately.

Note 2: factoring - it would be nice if the body of the 16 bit macro
didn't have to be replicated. I didn't see any easy way of doing
that, given the type requirements.

Note 3: argument types - if the function call version is used, the
type of the argument v must match the parameter. I count this as a
plus rather than a minus. The CPP expansion version can be used as a
fallback for first compile, comparison debugging, etc.

Note 4: on platforms that don't provide static inline functions, use
CPP directives to exclude the static inline function definitions and
to insure that EXPAND_in_puint_X_le_AS_CALLS will be 0. (That code
was left out above so as not to clutter the example.)


Step 3: if desired, calls such as

in_uint16_le( s, v );

can be changed to

in_puint16_le( s, &v );

where ever they appear. Similarly calls to in_uint32_le.


Disclaimer: I have test compiled code along the lines
of the above, but have not copied/pasted the exact code,
so there may be minor typographical erros.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,019
Latest member
RoxannaSta

Latest Threads

Top