0

I'm a "new" C programmer, but an old assembly programmer, and have been searching for an answer for a few days.

I'm trying to parse multiple fields in a message with the C struct construct, (It's a LORA radio with an embedded RTU modbus packet).

I have This example code that shows my question:

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

struct MessageTable{
    uint8_t msg_id;
    uint8_t from;
    uint8_t to;
    unsigned flags1 : 1;
    unsigned retransmitted : 1;
    unsigned hops : 4;
    union {
        unsigned long millisecs;
        unsigned char bytes[sizeof(unsigned long)];
    } ms;
};


struct MessageTable message, *mp;
struct MessageTable message_table[8] = {0};
char buf[256];

void main(void) {
    int i;
    for (i=0; i<255; i++)
        buf[i] = i;

    mp = (struct MessageTable) &buf;
    printf("To: %u, From: %u", mp->to, mp->from);
}

When I try to compile I get:

question.c: In function ‘main’:
question.c:27:18: error: conversion to non-scalar type requested
   27 |     mp = (struct MessageTable) &buf;
      |                  ^~~~~~~~~~~~

What I'm attempting to do is, overlay the struct in the buffer space at some arbitrary position for named access to the different fields instead of using hard coded offsets (I.E. to=buf[2]; and retransmitted = buf[3]&02x;

What is the clean, readable, appropriate way to do this?

NOTE: there will be multiple structs at different buf positions (LORA routing, Modbus Send, Modbus Rx, Modbus err, etc...) and, this is straight C, not C++.

I don't care if the buffer "runs off" the end of the struct, the code constructs take care of that.

Bill
  • 77
  • 8
  • `mp = (struct MessageTable*) buf;` (no `&`, `buf` is already a pointer due to array/pointer conversion [C11 Standard - 6.3.2.1 Other Operands - Lvalues, arrays, and function designators(p3)](http://port70.net/~nsz/c/c11/n1570.html#6.3.2.1p3)) The only change for the C17 standard is removal of `_Alignof` as an exception (wasn't needed before anyway) – David C. Rankin Dec 05 '20 at 00:15
  • `mp = (struct MessageTable*) buf;` note the addition of `MessageTable*` as well. Program Output: `To: 2, From: 1` – David C. Rankin Dec 05 '20 at 00:20

3 Answers3

3

First to address your error message on this line:

mp = (struct MessageTable) &buf;

Here you're attempting to convert &buf, which has type char (*)[256] i.e. a pointer to an array, to a struct MessageTable which is not a pointer type. Arrays in most contexts decay to a pointer to the first element, so you don't need to take its address, and you need to cast it to a pointer type:

mp = (struct MessageTable *)buf;

The other issue however is:

  • The struct might not be exactly the size you expect
  • The order of bitfieds may not be what you expect
  • If the buffer is not properly aligned for the fields in the struct you could generate a fault.
dbush
  • 205,898
  • 23
  • 218
  • 273
  • Yes, I understand that, it runs on an Atmel Tiny, (but I'm testing on Linux) so I see the alignment issues. I don't see that so far on the Tiny with GCC, and I can account for that, – Bill Dec 05 '20 at 00:38
  • I don't care about the physical positions of the bits if I can access them by name. (I'm writing both ends). – Bill Dec 05 '20 at 00:40
1

You have two problems in:

mp = (struct MessageTable) &buf;

The first is buf is already a pointer due to array/pointer conversion. C11 Standard - 6.3.2.1 Other Operands - Lvalues, arrays, and function designators(p3)

The second problem is you are casting to struct MessageTable instead of a Pointer to struct MessageTable. You can correct both with:

    mp = (struct MessageTable*) buf;

Also, unless you are programming in a freestanding environment (without the benefit of any OS), in a standards conforming implementation, the allowable declarations for main for are int main (void) and int main (int argc, char *argv[]) (which you will see written with the equivalent char **argv). See: C11 Standard - §5.1.2.2.1 Program startup(p1). See also: What should main() return in C and C++? In a freestanding environment, the name and type of the function called at program startup are implementation-defined. See: C11 Standard - 5.1.2.1 Freestanding environment

Putting it altogether you would have:

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

struct MessageTable{
    uint8_t msg_id;
    uint8_t from;
    uint8_t to;
    unsigned flags1 : 1;
    unsigned retransmitted : 1;
    unsigned hops : 4;
    union {
        unsigned long millisecs;
        unsigned char bytes[sizeof(unsigned long)];
    } ms;
};


struct MessageTable message, *mp;
struct MessageTable message_table[8] = {0};
char buf[256];

int main(void) {
    int i;
    for (i=0; i<255; i++)
        buf[i] = i;

    mp = (struct MessageTable*) buf;
    printf("To: %u, From: %u", mp->to, mp->from);
}

Example Use/Output

$ ./bin/struct_buf_overlay
To: 2, From: 1
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
0

C struct fields are, by default, not guaranteed to be immediately adjacent to one other, and furthermore bitfields can be reordered. Implementations are permitted to reorder bitfields and implement padding in order to efficiently meet system memory alignment requirements. If you need to guarantee that struct fields are positioned in memory immediately adjacent to one another (without padding) and in the order you specified, you need to look up how to tell your compiler to create a packed struct. This is not standard C (but it's necessary to ensure that what you're trying to accomplish will work--it might, but is not guaranteed, to work otherwise), and each compiler has its own way of doing it.

Kurt Weber
  • 176
  • 10
  • Good considerations, also worth looking at [Is gcc's __attribute__((packed)) / #pragma pack unsafe?](https://stackoverflow.com/q/8568432/3422102) – David C. Rankin Dec 05 '20 at 00:34