【问题标题】:C type punning questionC型双关语问题
【发布时间】:2011-09-16 03:34:44
【问题描述】:

如何使以下函数对 uint8_t、uint16_t、uint32_t、int8_t、int16_t、int32_t 和 float_t 通用?

我不喜欢在每种情况下都重复相同的逻辑,正如你所看到的。每种情况下的唯一区别是演员表。

理想情况下,我希望有一个符合 C 标准并因此具有可移植性的解决方案。欢迎任何想法。

谢谢。

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 = FALSE;
        }
        break;

      case TYPE_UINT32:
       ...
       break;

      case TYPE_INT8:
       ...
       break;

      case TYPE_INT16:
       ...
       break;

      case TYPE_INT32:
       ...
       break;

      case TYPE_FLOAT:
       ...
       break;
    }

  return isWithinLimits;
}

【问题讨论】:

  • *(UINT16*)pData &gt; (UINT16)limits.Max 怎么可能是真的?
  • @Let_Me_Be:我猜limits.max不一定总是65535!
  • 没错,每个键都有自己定义的最小/最大限制。
  • 我想你误会了。问题是 UINT16 永远不会比 2^16-1 大,而 UNIT32 永远不会比 2^32-1 大,等等......
  • @Let_Me_Be:你的问题的答案是*(UINT16*)pData &gt; (UINT16)limits.max 可以为真,例如,limits.max 等于 7 并且*(UINT16*)pData 大于 7。thegreendroid 不明白什么? 2^16-1 和limits.max 有什么关系?

标签: c void-pointers type-punning


【解决方案1】:

为什么不使用宏?

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

【讨论】:

  • 我确实考虑过使用宏,但它仍在生成相同的代码。我正在努力使该功能更有效。但是,看起来没有简单的方法可以实现我想要做的事情。无论如何谢谢:)
【解决方案2】:

在 C 语言中进行这样的泛型编程并不容易。如果您担心维护问题,那么这可能是适合使用宏的极少数情况。

【讨论】:

  • 很公平。我确实对这个问题进行了长时间的思考,但我无法提出我喜欢的解决方案。哦,好吧,我想我将不得不忍受它。出于维护目的,我想在这种情况下最好使用宏。谢谢:)
【解决方案3】:

该标准的下一个修订版 (C1x) 将增加对类型泛型表达式的支持(来自维基百科的示例):

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

gcc 有一些初步的 C1x 支持。我认为 _Generic 尚不支持,但请记住以备不时之需。

【讨论】:

    【解决方案4】:

    好吧,你可以提取演员表:

    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
    }
    

    或者,如果各种类型形成一个从 0 开始的连续值范围,您甚至可以创建两个函数指针数组并执行以下操作:

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

    注意事项:

    • 您仍然需要为每种类型编写两个函数,尽管如果您愿意,它们很容易宏生成。
    • 这个小代码似乎并不值得。
    • 我选择了int64_t,因为它能够表示您使用的所有整数类型的所有值,因此转换为int64_t 永远不会丢弃信息,也永远不会改变与做相关的比较结果源类型中的相同比较。但是如果你还想覆盖uint64_t,那么你不能对所有东西都使用相同的类型,因为没有整数类型可以表示所有整数类型的所有值。您还需要为float 提供一个单独的testLimitsf 函数,可能使用long double 作为通用类型以实现未来的灵活性。
    • [编辑:我刚刚意识到,假设 IEEE-754,double 实际上可以准确地表示您使用的所有类型的所有值。因此,如果有轻微的可移植性限制,您可以使用testLimitsf 处理所有内容并处理双打]
    • 您确定在比较之前将其转换为(例如)uint8_t 值得吗?该值在uint8_t 的范围内,在这种情况下您不需要转换,您只需进行比较即可。否则该值不在范围内,在这种情况下,模减少使比较有点毫无意义,除非在 0 和 -1 的特殊情况下。所以它可能是值得的,如果你没有说明的话,但它对我来说看起来很可疑。
    • 您在评论中说:“我正在努力提高该功能的效率”。这可能与此相反。在逻辑上可以内联 testLimits 以及对 switch 中的转换函数的调用,但我不会指望它。

    【讨论】:

    • 这绝对是迄今为止看起来最干净的解决方案,我喜欢它:) 我认为使用 int64_t 应该可以解决问题。程序代码空间在我的目标环境中非常宝贵,因此我试图在遵守标准的同时尽可能高效。
    猜你喜欢
    • 1970-01-01
    • 2014-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-09
    • 2021-05-15
    相关资源
    最近更新 更多