Help with function-like macros

Discussion in 'C Programming' started by Stephen Sprunk, Dec 31, 2004.

  1. 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

    --
    Stephen Sprunk "Stupid people surround themselves with smart
    CCIE #3723 people. Smart people surround themselves with
    K5SSS smart people who disagree with them." --Aaron Sorkin
    Stephen Sprunk, Dec 31, 2004
    #1
    1. Advertising

  2. Stephen Sprunk

    Michael Mair Guest

    Stephen Sprunk wrote:
    > 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
    --
    E-Mail: Mine is a gmx dot de address.
    Michael Mair, Dec 31, 2004
    #2
    1. Advertising

  3. Stephen Sprunk

    Michael Mair Guest

    Michael Mair wrote:
    >
    >
    > Stephen Sprunk wrote:
    >
    >> 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))


    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



    --
    E-Mail: Mine is a gmx dot de address.
    Michael Mair, Dec 31, 2004
    #3
  4. Michael Mair <> writes:

    > Stephen Sprunk wrote:

    ....
    >> 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.

    ....
    >> However, I'm not comfortable both swapping out the macros _and_
    >> rewriting hundreds of calls to each at the same time


    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
    Mark L Pappin, Jan 4, 2005
    #4
  5. Stephen Sprunk

    Tim Rentsch Guest

    "Stephen Sprunk" <> writes:

    > 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.
    Tim Rentsch, Jan 5, 2005
    #5
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    80
    Views:
    2,417
    Stephen J. Bevan
    Nov 7, 2003
  2. Replies:
    1
    Views:
    439
    Marco Antoniotti
    Oct 7, 2003
  3. Replies:
    5
    Views:
    494
  4. Michael T. Babcock

    Re: Explanation of macros; Haskell macros

    Michael T. Babcock, Nov 3, 2003, in forum: Python
    Replies:
    0
    Views:
    515
    Michael T. Babcock
    Nov 3, 2003
  5. Anthony de Almeida Lopes

    Macros within function-like macros?

    Anthony de Almeida Lopes, Dec 26, 2005, in forum: C Programming
    Replies:
    13
    Views:
    759
Loading...

Share This Page