【问题标题】:Guarantee exact data width in (void*) array in C保证 C 中 (void*) 数组中的精确数据宽度
【发布时间】:2013-01-10 01:09:08
【问题描述】:

我在 C 中有一个简单的函数,它提供了一个指向数据数组的 void* 指针。我知道这个内存块中每个单独数据点的大小(以字节为单位),并且需要保证我可以修改这个块中的每个数据点,而不会意外改变相邻的数据点。在本例中,我想将每个值减 1。

所有数据点都是 8 位、16 位或 32 位。

例如:

void myFunction(void* data, size_t arraySize, size_t widthPerDataPoint)
{
  if(!data)
    return -1;
  size_t w = widthPerDataPoint;
  int numPoints = arraySize / widthPerDataPoint;
  int i;
  for(i=0; i<numPoints; i++)
  {
    if(w==1)       // 8 bit
      (*((int8_t*)data + i))--;
    else if(w==2)  // 16 bit
      (*((int16_t*)data + i))--;
    else if(w==4)  // 32 bit
      (*((int32_t*)data + i))--;
  }
}

不幸的是,int8_t 等数据类型仅保证它们的最小大小,根据 C99 规范,而不是确切大小。有什么方法可以就地重新转换和修改数据并保证我不会破坏我的阵列或接触相邻的数据点?此外,是否有一种等效的技术可以以某种方式适用于其他数据宽度(即:24 位、60 位等)?

【问题讨论】:

  • 为什么不使用结构?使用结构的链表?
  • C 中的某些 API 使用 (void*) 来传递通用数据,并将其留给调用应用程序来确定与其他函数的数据格式。

标签: c arrays types


【解决方案1】:

代码似乎并非完全不合理。我个人可能会这样做:

 switch(widthPerDataPoint)
 {
    case 1:
       {
          int8_t *dptr = data;
          for(i = 0; i < numPoints; i++)
             dptr[i]--;
       }
       break;
    case 2:
       {
          int16_t *dptr = data;
          for(i = 0; i < numPoints; i++)
             dptr[i]--;
       }
       break;

    case 4:
       {
          int32_t *dptr = data;
          for(i = 0; i < numPoints; i++)
             dptr[i]--;
       }
       break;

    default:
       fprintf(stderr, "Someone gave the wrong width - width=%d\n", 
               widthPerDatapoint);
       break;
}

这里的好处是你不会在每个循环中得到一堆条件。无论如何,编译器可能会对其进行排序,但我并不总是相信编译器会解决这些问题——而且我认为它也更简洁一些。

【讨论】:

    【解决方案2】:

    根据定义,将T* 类型的指针递增n 会将其移位n * sizeof(T) 字节。因此,编译器向您保证一致性。不用担心。

    【讨论】:

    • 如果int8_t 可能大于 8 位,那将无济于事; sizeof (int8_t) 可能因一种实现而异。但实际上,OP的假设是不正确的;看我的回答。
    • @KeithThompson,但这正是重点。无论他的具体实现大小如何,内存访问都将与底层布局保持一致。
    【解决方案3】:

    int8_t 保证正好是 8 位,如果是 CHAR_BIT==1,则正好是 1 个字节。

    引用最新 C 标准的N1570 草案,第 7.20.1.1 节:

    typedef 名称 intN_t 指定有符号整数类型 宽度为 N,无填充位和二进制补码 表示。因此,int8_t 表示这种有符号整数类型 宽度正好是 8 位。

    尽管出于您的目的,使用uint8_tuint16_t 等可能更有意义。

    如果实现不支持具有所需特性的类型,则不会定义它们;您可以通过检查来检测这一点,例如:

    #include <stdint.h>
    #ifdef UINT8_MAX
    /* uint8_t exists */
    #else
    /* uint8_t doesn't exist */
    #endif
    

    (如果CHAR_BIT != 8,那么int8_tuint8_t都不会被定义。)

    标准仅保证最小尺寸的是[u]intleast_N_t[u]intfast_t 类型。

    您必须保证数组和其中的偏移量都针对您用来访问它的类型正确对齐。我想你已经在处理这个问题了。

    【讨论】:

    • 尽管int8_t 被保证为8 位,但不能保证编译器不会在包含int8_t 对象的较大字上发出读/修改/写循环。事实上,我相信 Alpha 平台需要这个。
    • @caf:没错,值得指出,但我没有看到任何迹象表明这对 OP 来说是个问题。
    • 我怀疑我对这个问题的了解可能比实际情况更多。
    • 我可以保证提供的数据正确对齐。不过,这对我来说仍然没有帮助,因为它没有解决 16 位和 32 位数字的情况。
    • @Dogbert:也许我错过了什么。 C 标准保证int16_tuint16_t 正好是16 位,而int32_tuint32_t 正好是32 位。 N1570 7.20.1.1 的标题是“精确宽度整数类型”。唯一真正的潜在问题是CHAR_BIT !+ 8,但这种情况很少见(对于托管实现几乎不存在),并且很容易通过assert 进行检查。
    【解决方案4】:

    下面的呢?

    1. 将值复制到任何大小的本地 int 中

    2. 对局部变量执行减量

    3. 使用适当的位掩码将数组中的位置清零 (例如,~0xFF 表示 8 位)

    4. '和'把局部变量放回数组中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-04-11
      • 1970-01-01
      • 2019-03-16
      • 2011-07-25
      • 2018-04-21
      • 2021-10-09
      • 2018-07-23
      • 2013-01-24
      相关资源
      最近更新 更多