0

I have a buffer and want to cast it as pointer to structure:

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

struct __attribute__((packed)) req {
    uint8_t a;
    uint16_t b;
    uint8_t *c;
};


void f(uint8_t *Buf)
{
    struct req *r = (struct req *)Buf;

    printf("a: %02x\n", r->a);
    printf("b: %04x\n", r->b);
    printf("c: %02x\n", r->c[0]);

}

int main()
{
    uint8_t buf[] = {0x01, 0x02, 0x03, 0x04 };

    f(buf);

    return 0;
}

The example won't work because the 4th byte is not address, but the actual data. I can "fix" this by manually set pointer:

r->c = &Buf[3];

Is there any way to do this with a cast?

Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
  • 1
    I don't know of any hardware where a pointer is a single byte, so for a starter the buffer is too small. And making the pointer point to itself doesn't solve *anything*. It's more like a band-aid on a broken leg. What are you **really** trying to do here? – Bo Persson Nov 07 '17 at 12:04
  • 1
    If you want an array inside the struct, perhaps you meant `uint8_t c[1];`? – unwind Nov 07 '17 at 12:06
  • No, that's undefined behaviour. By assigning to `r->c`, you are both clobbering the very buffer contents you are trying to point to and writing past the end of the array `buf`. In `f`, you could do `struct rec r;` `r.a = Buf[0];` `r.b = Buf[1] | (Buf[2] << 8); /* assuming little-endian */` `r.c = &Buf[3];`. – Ian Abbott Nov 07 '17 at 12:08
  • 3
    In addition to what others have said, accessing byte array through incompatible struct members is a *strict aliasing violation*, which is also undefined behaviour. Don't do that. – user694733 Nov 07 '17 at 12:34
  • What output do you expect? – Jabberwocky Nov 07 '17 at 12:49
  • To avoid UB due to strict aliasing issues, you should allocate the buffer dynamically (malloc). Personally, I use alloca (nonstandard) for this kind of stuff and hope for the best. – Petr Skocik Nov 07 '17 at 12:52
  • And possible unaligned access too... which is yet another kind of UB, though here the `packed` would make compiler to mostly generate code that is packed too... – Antti Haapala -- Слава Україні Nov 07 '17 at 12:56
  • If you intend to just send a piece of memory to disk or network, you must ensure it's just one piece of memory. Using a pointer like `c` virtually ensures that you have two pieces: one piece that contains `c`, the other piece that `c` points to. One possible solution is to declare `c` as `uint8_t c[MAX_C_SIZE]`. Then the contents of `c` is part of the same piece of memory as `a` and `b`. Of course, that opens you to all sorts of nastiness if you overrun the `c`'s boundary. –  Nov 07 '17 at 13:00
  • I'm trying to parse communication protocol, where is c is variable length payload. I'll just remove the pointer from the structure and assign it manually. – Stefan Mavrodiev Nov 07 '17 at 13:31
  • @StefanMavrodiev note that the criticism above was not *limited* to the flexible array member / pointer. – Antti Haapala -- Слава Україні Nov 09 '17 at 13:12

3 Answers3

3

Is there any way to do this with a cast?

No, for many reasons:

  • As you say, the byte stream does not correspond to the struct. The last item is not a pointer. The only time when you can use a cast is when you know that the actual data stored at the target location is of the same type as the destination variable.
  • The cast invokes undefined behavior since it violates the strict aliasing rule. Anything can happen.
  • The various data in the byte stream seems misaligned, which may or may not be an issue depending on system.

In this case, you have to manually de-serialize the data:

struct req r = { buf[0], 
                 (uint16_t)buf[1]<<8 | buf[2], // assumes big endian
                 &buf[3] };

And here we notice that the code is also endianess-dependent. So you must know the endianess of the system/protocol where this data came from, in order to correctly de-serialize it.

Lundin
  • 195,001
  • 40
  • 254
  • 396
0

Is there any way to do this with a cast?

Yes it is possible, with the following assumptions:

  • You need to take care of the struct alignment (compiler specific):
    #pragma pack(push, 1) ... #pragma pack(pop),
    __attribute__((packed)), etc.

  • You need to take care of the endianess between your architecture and the bytes in a buffer (make a conversion if needed)

What you can do is to use void* c instead of uint8_t* c in the struct and then cast void* to a explicit pointer type.

Example 1:

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

#pragma pack(push, 1)
typedef struct {
    uint8_t a;
    uint16_t b;
    void* c;
} req;
#pragma pack(pop)

void f(uint8_t* Buf)
{
    req* r = (req*)Buf;
    printf("a: %02x\n", r->a);
    printf("b: %04x\n", r->b);
    printf("c: %02x\n", ((uint8_t*)&r->c)[0]);
}
int main()
{
    uint8_t buf[] = { 0x01, 0x02, 0x03, 0x04 };
    f(buf);
    return 0;
}

Output:

a: 01
b: 0302
c: 04

Example 2:

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

#pragma pack(push, 1)
typedef struct {
    uint8_t a;
    uint16_t b;
    void* c;
} req;

typedef struct {
    uint8_t cmd;
    uint16_t value;
} SubPacket;
#pragma pack(pop)

void f(uint8_t* Buf)
{
    req* r = (req*)Buf;

    printf("a: %02x\n", r->a);
    printf("b: %04x\n", r->b);

    SubPacket* sub_packet = (SubPacket*)&r->c;

    printf("cmd: %02x\n", sub_packet->cmd);
    printf("value: %04x\n", sub_packet->value);

}

int main()
{
    uint8_t buf[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 };

    f(buf);

    return 0;
}

Output:

a: 01
b: 0302
cmd: 04
value: 0605
  • *What you can do is to use `void* c` instead of `uint8_t* c` in the struct and then cast `void*` to a explicit pointer type.* That makes absolutely no difference - it's still a violation of strict aliasing. [And dangerous even on supposedly-safe x86 systems where "it works"...](https://stackoverflow.com/questions/46790550/c-undefined-behavior-strict-aliasing-rule-or-incorrect-alignment) – Andrew Henle Nov 25 '20 at 13:38
-1
#include <stdint.h>
#include <stdio.h>

struct __attribute__((packed)) req {
    uint8_t a;
    uint16_t b;
    uint8_t c[]; /* <--- Use this notation to be able to achieve this */
};


void f(uint8_t *Buf)
{
    struct req *r = (struct req *)Buf;

    printf("a: %02x\n", r->a);
    printf("b: %04x\n", r->b);
    printf("c: %02x\n", r->c[0]);

}

int main()
{
    uint8_t buf[] = {0x01, 0x02, 0x03, 0x04 };

    f(buf);

    return 0;
}

Look here: How to include a dynamic array INSIDE a struct in C?

Bregell
  • 139
  • 10