C: 实现半字节数组



我正在尝试将16个无符号值填充到8个字节(64位)中,并使用类似数组的语法访问它们
"数组"中的每个条目都是一个半字节——4位长。(我计划存储的值永远不会超过15)。

我的第一次尝试是:

int main(int argc, char* argv[]) {
    union nibbles_array {
        uint64_t as_long;
        struct inner_array {
            unsigned entry0  : 4;
            unsigned entry1  : 4;
            unsigned entry2  : 4;
            unsigned entry3  : 4;
            unsigned entry4  : 4;
            unsigned entry5  : 4;
            unsigned entry6  : 4;
            unsigned entry7  : 4;
            unsigned entry8  : 4;
            unsigned entry9  : 4;
            unsigned entry10 : 4;
            unsigned entry11 : 4;
            unsigned entry12 : 4;
            unsigned entry13 : 4;
            unsigned entry14 : 4;
            unsigned entry15 : 4;
        } as_array;     
    } array;
    array.as_long = 0x0123456789abcdef;
    printf("%d n", array.as_array.entry0);
    printf("%d n", array.as_array.entry1);
    printf("%d n", array.as_array.entry2);
    printf("%d n", array.as_array.entry3);
    printf("%d n", array.as_array.entry4);
    return 0;
}

这个实现产生了两个问题:第一个问题是值以相反的顺序存储。当然,我可以按相反的顺序分配值以获得所需的结果:array.as_long = 0xfedcba9876543210,但我希望此代码是可移植的,而不是依赖于endianness。第二个问题是,在类似数组的语法中,我无法访问带有索引的半字节。

第二次尝试是这样的:

int main(int argc, char* argv[]) {
    uint64_t pseudo_array = 0x0123456789abcdef;
    #define Array(i) (unsigned)((pseudo_array & (0xfUL << i)) >> i)
    int i;
    for (i = 0; i < 16; ++i) {
        printf("%d ", Array(i));
    }
    printf("n");
    return 0;
}

以上可以解决第二个问题(类似数组的语法);现在我可以用索引访问"元素",但字节序的问题仍然存在,加上这会产生错误的输出:

15 7 11 13 14 15 7 11 13 6 3 9 12 14 15 7

  1. 为什么上面会产生这种输出
  2. 你能建议一种实现方式吗?它既允许我通过索引访问"数组",又能解决字节序问题

您的第一次尝试肯定存在可移植性问题:标准中没有定义位字段和内存中实际位之间的映射。这并不完全是一个endianness问题,也不能有类似数组的语法。

你的第二次尝试要便携得多。问题也不是endianness,而是您自己对数组的第n个元素的概念。宏是错误的,因为您没有按正确的位数移位:i必须乘以4。我建议这个宏符合你的理解:

#define Array(a, i)  ((unsigned)(((a) >> (60 - 4 * (i))) & 0xf))

不需要的输出是由于宏的定义方式错误造成的;你的意思是逐字节移动,而不是逐位移动。可以通过如下定义宏来实现所需的行为。

#define Array(i) (unsigned)((pseudo_array & ((uint64_t)0x0f << (4*i))) >> (4*i))

可以使用一个动态分配的数组、一组用于访问数组的单个"索引"的函数,以及使用较大类型(如uint64_t)设置或获取"数组"的较大部分的函数,而不是将"向量"存储为位字段。

类似以下API

// The "na" prefix stands for Nibble Array
struct nibble_array
{
    uint8_t *data;  // The actual "array" of nibbles
    size_t   size;  // Current size of "array" (number of elements)
};
// Create an array containing a number of elements
struct nibble_array *na_create(const size_t elements);
// Get a value from specified index in array
int na_get(const struct nibble_array *array, const size_t index);
// Set a specified index in the array to a value
void na_set(struct nibble_aray *array, const size_t index, const int value);
// Get the number of elements (nibbles) in the array
size_t na_size(const struct nibble_array *array);
// Set a larger part of the array to some values
void na_set64(struct nibble_array *array, size_t start_index, const uint64_t value);

如果您希望数据是私有的,则不必提供nibble_array结构的定义(请阅读不透明指针)。

这样的东西怎么样:

#include <stdio>
int main(int argc, char* argv[]) {
    unsigned char array[8] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };
    #define Array(i) (i%2 ? (array[i/2] & 0x0F) : ((array[i/2] & 0xF0) >> 4))
    int i;
    for (i = 0; i < 16; ++i) {
        printf("%d ", Array(i));
    }
    printf("n");
    return 0;
}

我相信@chqrlie在endianness问题上是正确的,但我认为只使用数组会更容易。

uint8_t ary[20]; /* 40 nibbles */
#define getnibblelen(ary) (sizeof(ary) * 2) /* works because it's an ARRAY of uint8_t, wouldn't work if you're passing the array to a function. */
#define getnibble(ary, idx) (idx % 2 ? /* if odd */ ary[idx/2] & 0xf0 >> 4 : /* if even */ ary[idx/2] & 0x0f)
#define setnibble(ary, idx, val) (idx % 2 ? ary[idx/2] = (ary[idx/2] & 0x0f) | ((val & 0x0f) << 4) : ary[idx/2] = (ary[idx/2] & 0xf0) | (val & 0x0f))

最新更新