0

I have the following struct definition:

typedef struct mb32_packet_t {
    union {
        struct {
            uint16_t preamble;
            uint8_t  system_id;
            uint8_t  message_id;
            uint8_t  reserved;
            uint32_t paylen;
        };
        uint8_t header[9];
    };
    uint8_t *payload;
    uint16_t checksum;
} __attribute__((packed)) mb32_packet_t;

Now I would like to have another union, so that I can get an uint8_t body[] pointer to the entire packet object. Something like this:

typedef struct mb32_packet_t {
    union {
        struct {
            union {
                struct {
                    uint16_t preamble;
                    uint8_t  system_id;
                    uint8_t  message_id;
                    uint8_t  reserved;
                    uint32_t paylen;
                };
                uint8_t header[9];
            };
            uint8_t *payload;
            uint16_t checksum;
        };
        uint8_t body[?];
    };
} __attribute__((packed)) mb32_packet_t;

The problem is that the payload field size is dynamically determined at runtime. Is there another way to accomplish this other than making payload fixed sized?


I basically want to send objects of this type through a network socket, so I need a uint8_t pointer that points to an object of this type. At the time of sending the object, I know the size of the entire object in bytes.

salocinx
  • 3,715
  • 8
  • 61
  • 110
  • 1
    Your struct only holds a (fixed-sized) pointer to the payload, not the (dynamically-sized) content it points to. – Ruud Helderman Mar 12 '20 at 13:28
  • 1
    Given some `x` whose type is `mb32_packet_t`, you can get a pointer to its bytes with `(uint8_t *) &x`. There is no need to define a new structure or union member for this. A flexible array member arguably could be used for this, but that is not a good solution for multiple reasons. – Eric Postpischil Mar 12 '20 at 13:34
  • Just to check: do you understand the difference between a pointer and an array? (No offence intended; there are many questions asked by people who are new to C) – user253751 Mar 12 '20 at 13:43
  • @user253751 Probably not entirely. A pointer can point to any array item's memory address, whereas an array is a construct to hold multiple items, including features like indexing for example. – salocinx Mar 12 '20 at 13:50
  • That's a good intro. Are you aware that in many practical contexts the two can be used interchangeably? – Mad Physicist Mar 12 '20 at 14:00
  • You can do `payload[2]` or whatever directly since the size of the object is defined, the offset can be computed the same for a heap pointer as for a stack array – Mad Physicist Mar 12 '20 at 14:01
  • I basically want to send objects of this type through a network socket, so I need a uint8_t pointer that points to this object. At the time of sending the object, I know the size of the entire object in bytes. – salocinx Mar 12 '20 at 14:04
  • And note that `__attribute__((packed))` can be [dangerous](https://stackoverflow.com/questions/8568432/is-gccs-attribute-packed-pragma-pack-unsafe). It leads to misaligned accesses, which is undefined behavior [that can fail even on x86 systems](https://stackoverflow.com/questions/46790550/c-undefined-behavior-strict-aliasing-rule-or-incorrect-alignment/57326681#57326681). I personally aim for a standard of reliability in the code that I write that's higher than, "I hope this doesn't blow up in the future." – Andrew Henle Mar 12 '20 at 14:21
  • @salocinx When you have a pointer inside another object, the pointer is part of the object, but the thing it points to is not. Therefore, with your plan you cannot use a pointer. – user253751 Mar 12 '20 at 14:24
  • @user253751 Okay, I think I understand now. The `mb32_packet_t` object itself only holds the pointer to `payload`, not the data itself. The uint8_t array it will later point to, is not an integral part of the `mb32_packet_t` object. Therefore it's not possible to get a pointer that points to a continuous memory area that holds the entire object. My goal is to have a uint8_t pointer that points to the entire object, including the `payload` so that I can send the entire packet (header+payload+checksum) through a tcp socket. – salocinx Mar 12 '20 at 14:33
  • Where did you get the requirements for this `mb32_packet_t` structure? That is, how do you know it needs a `uint16_t preamble` and such? You have a couple of things in your question that do not look right, and telling us the context where these come from would help us to figure out and explain. – Eric Postpischil Mar 12 '20 at 15:39

3 Answers3

3

Introduction

The question is unclear, so I will discuss three apparent possibilities.

Fixed-length header followed by variable-length payload

A typical way to define a packet for a networking or messaging service is to have a fixed-length header followed by a variable-length payload. In modern C, the variable-length payload may be defined using a flexible array member, which is an array with no dimension at the end of a structure:

typedef struct
{
    uint16_t preamble;
    uint8_t  system_id;
    uint8_t  message_id;
    uint8_t  reserved;
    uint32_t paylen;
    uint8_t payload[];
} mb32_packet_t;

Memory for such a structure is allocated use the base size provided by sizeof plus additional memory for the payload:

mb32_packet_t *MyPacket = malloc(sizeof *MyPacket + PayloadLength);

When you pass such an object to a routine that requires a char * or uint8_t * or similar type for its argument, you can simply convert the pointer:

SendMyMessage(…, (uint8_t *) MyPacket,…);

That cast, (uint8_t *) MyPacket, provides the pointer to the first byte of the packet requested in the question. There is no need to wedge another member into the structure or layer on a union or other declaration.

Prior to the introduction of flexible array members in C 1999, people would use one of two workarounds to create structures with variable amounts of data. One, they might just define a member array with one element and adjust the space calculations accordingly:

typedef struct
{
    …
    unsigned char payload[1];
} mb32_packet_t;

mb32_packet_t *MyPacket = malloc(sizeof *MyPacket + PayloadLength - 1);

Technically, that violated the C standard, since the structure contained an array of only one element even though more space was allocated for it. However, compilers were not as aggressive in their analysis of program semantics and their optimization as they are now, so it generally worked. So you may still see old code using that method.

Two, GCC had its own pre-standard implementation of flexible array members, just using an array dimension of zero instead of omitting a dimension:

typedef struct
{
    …
    unsigned char payload[0];
} mb32_packet_t;

Again, you may see old code using that, but new code should use the standard flexible array member.

Fixed-length header with pointer to variable-length payload

The payload-after-header form shown above is the form of packet I would most expect in a messaging packet, because it matches what the hardware has to put “on the wire” when sending bytes across a network: It writes the header bytes followed by the data bytes. So it is convenient to have them arranged that way in memory.

However, your code shows another option: The data is not in the packet but is pointed to by a pointer in the packet, with uint8_t *payload;. I would suspect that is a mistake, that the network or messaging service really wants a flexible array member, but you show it followed by another member, uint16_t checksum. A flexible array member must be the last member in a structure, so the fact that there is another member after the payload suggests this definition with a pointer may be correct for the messaging service you are working with.

However, if that is the case, it is not possible to get a pointer to the complete packet object, because the object is in two pieces. One contains the header, and the other, at some unrelated location in memory, contains the data.

As above, you can produce a uint8_t * pointer to the start of the packet with (uint8_t) MyPacket. If the messaging system knows about the pointer in the structure, that should work. If you have mistaken what the packet structure must be, it will fail.

Fixed-length header followed by fixed-length payload space

Code elsewhere on Stack Overflow shows a struct mb32_packet_t with a fixed amount of space for a payload:

typedef struct mb32_packet_t {
  uint8_t compid;
  uint8_t servid;
  uint8_t payload[248];
  uint8_t checksum;
} __attribute__((packed)) mb32_packet_s;

In this form, the packet is always a fixed size, although the amount of space used for the payload could vary. Again, you would obtain a uint8_t * pointer to the packet by a cast. There is no need for a special member for that.

Community
  • 1
  • 1
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • Thanks a lot for this comprehensive answer! I studied every detail very carefully and did learn quite some new things. I now went the "flexible array member" route and everything is working fine! Btw. I am in control of both endpoints, so highly flexible in designing the network protocol. You helped me a lot - thank you! – salocinx Mar 13 '20 at 18:50
0

This is possible, but not with a struct or union, because all parts of a struct or union need to have a known size. You can still use a struct for the header.

Because the body starts at a known location, there's a trick you can use to access it as if it was part of the structure. You can declare it with no size at all (a "flexible array member") or as 0 bytes (a GCC extension that predates the standard). The compiler will not allocate any space for it, but it will still let you use the name to refer to the end of the struct. The trick is that you can malloc extra bytes after the end of the struct, and then use body to refer to them.

typedef struct mb32_packet_t {
    union {
        struct {
            uint16_t preamble;
            uint8_t  system_id;
            uint8_t  message_id;
            uint8_t  reserved;
            uint32_t paylen;
        };
        uint8_t header[9];
    };
    uint8_t body[]; // flexible array member

} __attribute__((packed)) mb32_packet_t;

// This is not valid. The body is 0 bytes long, so the write is out of bounds.
mb32_packet_t my_packet;
my_packet.body[0] = 1;

// This is valid though!
mb32_packet_t *my_packet2 = malloc(sizeof(*my_packet2) + 50);
my_packet2->body[49] = 1;

// Alternative way to calculate size
mb32_packet_t *my_packet3 = malloc(offsetof(mb32_packet_t, body[50]));
my_packet3->body[49] = 1;

The flexible array member must be last. To access the checksum, you will need to allocate an extra 2 bytes, and use pointer arithmetic. Fortunately, this is just for the checksum, and not the entire header.

mb32_packet_t *my_packet = malloc(sizeof(*my_packet) + body_size + 2);
uint16_t *pchecksum = (uint16_t*)&my_packet.body[body_size];
// or
uint16_t *pchecksum = (uint16_t*)(my_packet.body + body_size);

After you fill in the header, body and checksum, then because they are contiguous in memory, a pointer to the header is also a pointer to the entire packet object.

user253751
  • 57,427
  • 7
  • 48
  • 90
-1

I usually do it this way:

typedef struct 
{
    size_t payload_size;
    double x;
    char y[45];
    /* another members */
    unsigned char payload[];
}my_packet_t;

or if your compiler does not support FAMs

typedef struct 
{
    size_t payload_size;
    double x;
    char y[45];
    /* another members */
    unsigned char payload[0];
}my_packet_t;

So it the payload can be at the end of the header structure

0___________
  • 60,014
  • 4
  • 34
  • 74