Search code examples
carraysstructampersand

Accessing an array from a struct and casting it to a pointer


struct message {
  uint8_t start;
  uint16_t length;
  uint8_t data[10];
  uint8_t checkSum;
} __attribute__((packed));

struct message devices[10];

void request(struct message *msg) {
  struct request *req = (struct request *)&msg->data;
  req->operation = 1;
  req->requesterAddress = MASTER_ADDRESS;      
}

request(&devices[0]);

My question is, why the ampersand at "&msg->data" ?

My understanding is that the function "request" receives a pointer "msg" to a struct. msg->data retrieves the pointer "data" from the struct pointed by "msg" ("data" is an array), and it is then casted to another pointer type (struct request *).

So that part should be (struct request *)msg->data; So why the ampersand(&)?


Solution

  • Well, this code is not C because __attribute__((packed)) is only valid in gcc and some other specific implementation but definitely does not exist in standard C.

    But provided sizeof(struct message) <= 10, the rest of the code is correct code according to 4. Conformance but contains unspecied behaviour and may contain undefined behaviour depending of the implementation

    • request(&devices[0]); and void request(struct message *msg) {...}: Ok: request expects a struct message * and receive the address of first element of an array of struct message- all is fine here
    • struct request *req = (struct request *)&msg->data;: that is the most interesting part.
      • msg->data is a char array, that is an aggregate. The address of an aggregate is the address of its first byte. Said differently (char *) &msg-> data (address of first byte of the aggregate) is the same as msg->data(array decaying to a pointer to its first element)
      • (struct request *)&msg->data; the pointer to char is casted to a pointer to struct request. Depending of the implementation, nothing guarantees that the pointer will be correctly aligned. If it is not, this is undefined behaviour according to 6.3.2.3 Pointers § 7. If is is, we still have unspecified behaviour according to 6.5 Expressions § 6-7, because the new pointer will be used to store an element that has not the declared type of char[10]. But this will be accepted by all known implementation, because internally they process the same char arrays (explicit type) and allocated memory (no declared type) - simply because they need to implement malloc (see Is it possible to write a conformant implementation of malloc in C?)

    The rest of the code contains no other problem. But it should be noticed that if the __attribute__(packed) is honoured by the implementation, the field data will be the third byte of the struct, which gives a weird alignment. That can lead to crashes on implementation that require strict alignment for certain types.


    References from n1256 draft for C99

    1. Conformance
      ...
      2 If a ‘‘shall’’ or ‘‘shall not’’ requirement that appears outside of a constraint is violated, the behavior is undefined...
      3 A program that is correct in all other aspects, operating on correct data, containing unspecified behavior shall be a correct program...

    6.3.2.3 Pointers
    ...
    7 A pointer to an object or incomplete type may be converted to a pointer to a different object or incomplete type. If the resulting pointer is not correctly aligned for the pointed-to type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer. When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object.

    6.5 Expressions
    ...
    6 The effective type of an object for an access to its stored value is the declared type of the object, if any. If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value. If a value is copied into an object having no declared type using memcpy or memmove, or is copied as an array of character type, then the effective type of the modified object for that access and for subsequent accesses that do not modify the value is the effective type of the object from which the value is copied, if it has one. For all other accesses to an object having no declared type, the effective type of the object is simply the type of the lvalue used for the access.

    7 An object shall have its stored value accessed only by an lvalue expression that has one of the following types:

    • atype compatible with the effective type of the object,
    • aqualified version of a type compatible with the effective type of the object,
    • a type that is the signed or unsigned type corresponding to the effective type of the object,
    • a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
    • an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
    • a character type.