Niklas Norin
Niklas Norin

Reputation: 365

Initialize C array with struct

Is it possible to initialise an array of uint8_t with a struct?

What I want to achieve is something similar to:

#define BIGGER_THAN_STRUCT 1024    

struct Device {
  uint32_t address;
  uint32_t id;
};

const uint8_t bytes[BIGGER_THAN_STRUCT] = (struct Device) {
  .address = 123,
  .id = 456,
};

The reason I want to do this it to get an easy overlay view of the content I write to the byte array. I simply want an easy interface to whatever first bytes I need for the information that is displayed by the structure.

If thats not possible, what's the closest thing to it?

Upvotes: 3

Views: 1574

Answers (7)

alk
alk

Reputation: 70981

Beside approaching this via a union (as proposed by hdante here https://stackoverflow.com/a/27462808/694576) instead of trying:

const uint8_t bytes[BIGGER_THAN_STRUCT] = (struct Device) {
  .address = 123,
  .id = 456,
};

do quick and dirty:

uint8_t bytes[BIGGER_THAN_STRUCT] = {0};
*((struct Device *) bytes) = ((struct Device) {
  .address = 123,
  .id = 456,
});

or better do:

struct Device dev = {
  .address = 123,
  .id = 456,
};

uint8_t bytes[BIGGER_THAN_STRUCT] = {0};

...

size_t size_dev = sizeof dev;
memcpy(bytes, &dev, size_dev);

Then inspect array bytes up to the size_dev - 1th element.

Upvotes: 1

hdante
hdante

Reputation: 8030

The standard way in C to overlay data types is by using unions:

    #include <stdio.h>
    #include <stdint.h>
    #define BIGGER_THAN_STRUCT 1024    

    struct Device {
      uint32_t address;
      uint32_t id;
    };

    union Memory {
            uint8_t bytes[BIGGER_THAN_STRUCT];
            struct Device devices[BIGGER_THAN_STRUCT/sizeof(struct Device)];
    };

    const union Memory memory = {
            .devices = {
                    { .address = 123, .id = 30 },
                    { .address = 111, .id = 89 }
            }
    };

    int main(void)
    {
            unsigned i;

            for (i = 0; i < 16; i++)
                    printf("%d ", memory.bytes[i]);

            putchar('\n');

            return 0;
    }

,

$ ./a 
123 0 0 0 30 0 0 0 111 0 0 0 89 0 0 0 

Upvotes: 3

David C. Rankin
David C. Rankin

Reputation: 84642

There is an alternative to using a uint8_t byte[] array. You can also make use of a struct utilizing a bitfield for each addr and id. You (may/may not) find it more convenient, but it does provide an easy way to keep the offset information associated with any give addr/id pair.

I don't believe there is a way to directly make use of struct type Designated Initializers to fill the uint8_t byte array. I think the closest full initialization would be with memcpy. I've included that in the example below. Note, there is nothing that prevents you from filling the uint8_t byte array with memcpy, but then you have to track the offset within the uint8_t byte array to accurately point to any given byte in either addr or id for any given element. This is where the bitfield makes things a little easier. You get a one-to-one correlation between the struct Device index and the uibitfield index with a1..4 and b1..4 being the bytes within each addr and id, respectively.

A version using the uint8_t array is shown below this version.

Here is a short example with test data in an array of struct Device:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

typedef struct                      /* bitfield corresponding to struct Device */
{
    unsigned int  a1 : 8,
                  a2 : 8,
                  a3 : 8,
                  a4 : 8;

    unsigned int  b1 : 8,
                  b2 : 8,
                  b3 : 8,
                  b4 : 8;

} uibitfield;

struct Device {                     /* original struct Device   */
    uint32_t addr;
    uint32_t id;
};

int main () {

    /* test data in an array of struct Device   */
    struct Device dev[] = { {0x4009f0, 0}, {0x4009f1, 1}, {0x4009f2, 2}, {0x4009f3, 3}, 
                            {0x4009f4, 4}, {0x4009f5, 5}, {0x4009f6, 6}, {0x4009f7, 7}, 
                            {0x4009f8, 8}, {0x4009f9, 9}, {0x4009fa, 10}, {0x4009fb, 11}, 
                            {0x4009fc, 12}, {0x4009fd, 13}, {0x4009fe, 14}, {0x4009ff, 15}, 
                            {0x400a00, 16}, {0x400a01, 17}, {0x400a02, 18}, {0x400a03, 19} };

    int it = 0;                             /* general iterator */
    size_t sz = sizeof (dev)/sizeof (*dev); /* size of array    */

    /* create validate and fill bitfield array */
    uibitfield *bytes = calloc (sz, sizeof (*bytes));
    if (!bytes) {
        fprintf (stderr, "error: allocation failed.\n");
        return 1;
    }
    memcpy (bytes, dev, sz * sizeof (dev));

    /* print bytes in each addr & id in dev */
    for (it = 0; it < sz; it++)
        printf ("\n  addr[%2d]:  0x%02x, 0x%02x, 0x%02x, 0x%02x\n    id[%2d]:  0x%02x, 0x%02x, 0x%02x, 0x%02x\n",
                it, (bytes + it)->a1, (bytes + it)->a2, (bytes + it)->a3, (bytes + it)->a4,
                it, (bytes + it)->b1, (bytes + it)->b2, (bytes + it)->b3, (bytes + it)->b4);

    printf ("\n");

    return 0;
}

output:

$ ./bin/memview

  addr[ 0]:  0xf0, 0x09, 0x40, 0x00
    id[ 0]:  0x00, 0x00, 0x00, 0x00

  addr[ 1]:  0xf1, 0x09, 0x40, 0x00
    id[ 1]:  0x01, 0x00, 0x00, 0x00

  addr[ 2]:  0xf2, 0x09, 0x40, 0x00
    id[ 2]:  0x02, 0x00, 0x00, 0x00

  addr[ 3]:  0xf3, 0x09, 0x40, 0x00
    id[ 3]:  0x03, 0x00, 0x00, 0x00

  addr[ 4]:  0xf4, 0x09, 0x40, 0x00
    id[ 4]:  0x04, 0x00, 0x00, 0x00

  (snip)

Note: it was a unclear how you would be using/filling struct Device and how much of an initial peek you wanted at the data in stuct Device, so this is just intended as an example of viewing the data.


using a uint8_t byte array:

If you do want to use the `uint8_t array, the changes needed are minimal:

    /* using  a uint8_t byte array    */
    uint8_t *bytearr = calloc (sz * 4, sizeof (*bytearr));
    if (!bytearr) {
        fprintf (stderr, "error: allocation failed.\n");
        return 1;
    }
    memcpy (bytearr, dev, sz * sizeof (dev));

    /* print bytes in each addr & id in dev using uint8_t array */
    for (it = 0; it < sz * 4; it+=8)
        printf ("\n  addr[%2d]:  0x%02x, 0x%02x, 0x%02x, 0x%02x\n    id[%2d]:  0x%02x, 0x%02x, 0x%02x, 0x%02x\n",
                it, bytearr[it], bytearr[it+1], bytearr[it+2], bytearr[it+3],
                it, bytearr[it+4], bytearr[it+5], bytearr[it+6], bytearr[it+7]);

output is the same

Upvotes: 0

Doncho Gunchev
Doncho Gunchev

Reputation: 2239

Does this:

#define BIGGER_THAN_STRUCT 1024    

struct Device {
  uint32_t address;
  uint32_t id;
};

struct DeviceAndData {
  struct Device d;
  char filler[BIGGER_THAN_STRUCT - sizeof(Device)];
};
const struct DeviceAndData bytes_pre = { .d = { .address = 123, .id = 456 } };
const uint8_t* bytes = (uint8_t*)&bytes_pre;

do the trick? :)

Upvotes: 0

ezaquarii
ezaquarii

Reputation: 1916

Usually, when I have to work with bag of structured bytes, I create a "view" struct/class that gives me some higher-level interface to a block of memory. Manual pointer arithmetic is usually too error prone to be repeated. Create a "memory view" structure and unit-test it properly.

struct memory_view {
    uint32_t *addr;
    uint32_t *id;
};

void view_init(struct memory_view *view, void *buf, size_t bufsz) {
    // TODO: validate buffer size using bufsz here
    view->addr = (uint32_t*)buf;
    view->id = (uint32_t*)(buf + sizeof(uint32_t));
}

struct memory_view view;
uint8_t buffer[LARGE_NUMBER];
view_init(&view, buffer, LARGE_NUMBER);

*view->addr = 0xDEADBEEF;
*view->id = 0xCAFEBABE;

You can see a similar technique in device drivers, when structures are initialized to access different hardware registers located in some memory region.

You could also get a buffer pointer, cast it to structure and try using this memory block as it were a structure. Doable, but memory alignment can bite you hard. Such code may or may not work, depending on compiler and system architecture.

Upvotes: 0

Theofilos Mouratidis
Theofilos Mouratidis

Reputation: 1156

I think you maybe want to do something like that, even if the copy is not that necessary as b_sample is exactly what you need.

#include <stdio.h>
#include <stdint.h>

typedef struct Device dev;
struct Device {
  uint32_t address;
  uint32_t id;
};

int main(void) {
    //create an instance `sample.address=123` and `sample.id=456`
    dev sample = (dev) { 123, 456 };
    //convert dev pointer to byte pointer, so you loop through bytes
    uint8_t* b_sample = (uint8_t *)(&sample);
    //buffer for copy
    uint8_t* bytes[1024];

    int size = (int)(sizeof(dev)/sizeof(uint8_t)), i;
    for(i = 0; i < size; i++) {
        bytes[i] = b_sample[i];
        //see what values you copy
        printf("%x ", bytes[i]);
    }

    return 0;
}

Demo: http://codepad.org/wE8dbBV1

If you want to divide the struct into uint16_t segments you can safely replace all uint8_t with uint16_t

Upvotes: 0

Iharob Al Asimi
Iharob Al Asimi

Reputation: 53026

This will do it then

static const uint8_t buffer[BIGGER_THAN_STRUCT] = {
    0x7b, 0x00, 0x00, 0x00, 
    0xc8, 0x01, 0x00, 0x00
};

Upvotes: 0

Related Questions