简体   繁体   中英

C: Implementing array of nibbles

I am trying to stuff 16 unsigned values into 8 bytes (64 bit), and access them using an array-like syntax.
Every entry in the "array" will be one nibble - 4 bit long. (The values I plan to store are never bigger than 15).

My first attempt was this:

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;
}

Two problems arises from this implementation: the first is that the values are stored in reverse order. I can, of course, assign the values in reverse order to get the desired result: array.as_long = 0xfedcba9876543210 , but I want this code to be portable, and not endianness-dependent. The second is that I can't access the nibbles with an index, in an array-like syntax.

The second attempt was this:

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;
}

The above can solve the second problem (array-like syntax); now I can access "elements" with index, but the problem of endianness remains, plus this produces wrong output:

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

  1. Why is the above produces this output?
  2. Can you please suggest implementation that will both allow me to access the "array" by index and will solve the endianness problem?

Your first attempt definitely has portability issues: the mapping between bitfields and actual bits in memory is not defined in the Standard. It is not exactly an endianness issue, and you cannot have an array like syntax either.

Your second attempt is much more portable. The issue is not endianness either, but your own conception of what is the n-th element of the array . The macro is erroneous because you do not shift by the correct number of bits: i must be multiplied by 4. I would suggest this macro to fit your understanding:

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

The undesired output is caused by the fact that the macro is defined in the wrong way; you mean to shift nibble-wise , not bit-wise . The desired behaviour can be achieved by defining the macro as follows.

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

Instead of storing the "vector" as bitfields, could use a dynamically allocated array, and a set of functions for accessing individual "indexes" of your array as well as functions to set or get larger parts of the "array" using larger types (like eg uint64_t ).

Something like the following 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);

You don't have to provide the definition of the nibble_array structure if you want the data to be private (read about opaque pointers ).

How about something like this:

#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;
}

I believe @chqrlie is correct concerning endianness issues, but I think it would be easier to just use an array.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM