开发者

C type punning question

How do I make the below function generic for uint8_t, uint16_t, uint32_t, int8_t, int16_t, int32_t and float_t?

I don't like repeating the same logic in every case as you can see. The only difference in each case is the casting.

I'd ideally like a solution that adheres to the C standard and is hence portable. Any ideas are welcome.

Thanks.

static bool_t IsWithinLimits(const dbKey_t *key, const void *data)
{
    bool_t isWithinLimits = TRUE;
    limits_t limits = getDefinedLimits(key);

    switch(key->type)
    {
      case TYPE_UINT8:
        if((*(const UINT8*)data > (UINT8)limits.max) || (*(const UINT8*)data < (UINT8)limits.min))
        {
          isWithinLimits = FALSE;
        }
        break;

      case TYPE_UINT16:
        if((*(UINT16*)pData > (UINT16)limits.max) || (*(UINT16*)data < (UINT16)limits.min))
        {
          isWithinLimits = FA开发者_运维问答LSE;
        }
        break;

      case TYPE_UINT32:
       ...
       break;

      case TYPE_INT8:
       ...
       break;

      case TYPE_INT16:
       ...
       break;

      case TYPE_INT32:
       ...
       break;

      case TYPE_FLOAT:
       ...
       break;
    }

  return isWithinLimits;
}


There is no easy way to do generic programming like this in C. If you're worried about maintenance, then this may be a rare occasion where a macro is appropriate.


Why not use a macro?

#define DO_MY_WORK(TYPE)\
  if((*(TYPE*)pData > (TYPE)tLimits.zMax) || (*(TYPE*)pData < (TYPE)tLimits.zMin))\
  {\
      isWithinLimits = FALSE;\
  }


Well, you could extract the casts:

int64_t loadptr_uint8(const void *p)  {
    return *(uint8_t*)p;
}
int64_t convert_uint8(int64_t val) {
    return (uint8_t)val;
}

int testLimits(const limits_t *plimits, const void *pData, int64_t(*loadptr)(void*), int64_t (*convert)(int64_t)) {
    return loadptr(pData) <= convert(limits->max) && loadptr(pData) >= convert(limits->min);
}

switch(key->type) {
    case TYPE_UINT8:
        isWithinLimits = testLimits(&limits, pData, loadptr_uint8, convert_uint8);
        break;
    // etc
}

Or, if the various types form a contiguous range of values from 0, you could even make two arrays of function pointers and do:

bool isWithinLimits = testLimits(&limits, pData, loadptrs[key->type], converts[key->type]);

Notes:

  • You still have to write two functions for each type, although they're easily macro-generated if you prefer.
  • It doesn't really seem worth it for this little code.
  • I've chosen int64_t since it is capable of representing all the values of all the integer types you use, so the conversions to int64_t never discard information and never change the result of a comparison with respect to with doing the same comparison in the source type. But if you also wanted to cover uint64_t, then you can't use the same type for everything, since there is no integer type that can represent all the values of all integer types. You'd also need a separate testLimitsf function for float, perhaps using long double as the common type for future flexibility.
  • [Edit: I just realised, assuming IEEE-754, double actually can exactly represent all the values of all the types you use. So with a slight portability restriction, you could use testLimitsf for everything and deal in doubles]
  • Are you sure that it's worth converting to (for example) uint8_t before comparison? Either the value is in range for a uint8_t, in which case you don't need to convert, you can just do the comparison. Or else the value isn't in range, in which case the modulo reduction makes the comparison a bit meaningless except in the special cases of 0 and -1. So it might be worth it, if something you haven't stated makes it so, but it looks fishy to me.
  • You said in a comment, "I am trying to make that function more efficient". This might go against that. It's logically possible to inline testLimits and also the calls to the casting functions in the switch, but I wouldn't count on it.


The next revision of the standard (C1x) will add support for type generic expressions (example from wikipedia):

#define cbrt(X) _Generic((X), long double: cbrtl, \
                              default: cbrt, \
                              float: cbrtf)(X)

gcc has some preliminary C1x support. I think _Generic is not yet supported, but keep it in mind for the future.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜