Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does copying a structure by a direct assignment fail?

I am running into an Hard Fault Exception while copying some data on a micro-controller from one struct to another. I tried different implementations which should do all the same. See my code lines:

memcpy(&msg.data, data, 8);
memcpy(&msg.data, data, sizeof(*data));
memcpy(&msg.data, data, sizeof(msg.data));
msg.data = *data;  // Hard Fault

The first three lines are working pretty fine. The last one ends with an Hard Fault Exception. The assembly for the lines with memcpy is the same. The assembly for the direct assignment differs:

  memcpy(&msg.data, data, sizeof(msg.data));
 800c480:   f107 030c   add.w   r3, r7, #12
 800c484:   330b        adds    r3, #11
 800c486:   2208        movs    r2, #8
 800c488:   6879        ldr r1, [r7, #4]
 800c48a:   4618        mov r0, r3
 800c48c:   f7f4 f82e   bl  80004ec <memcpy>
  msg.data = *data;                  // Hard Fault
 800c490:   687b        ldr r3, [r7, #4]
 800c492:   f107 0217   add.w   r2, r7, #23
 800c496:   cb03        ldmia   r3!, {r0, r1}
 800c498:   6010        str r0, [r2, #0]
 800c49a:   6051        str r1, [r2, #4]

I am using the GNU Arm Embedded Toolchain 5.4.1 20160919.

Here is a minimal code example which (hopefully) shows the the problem. The data structure msg_t must use the packed attribute to match some hardware registers. On the micro-controller this codes ends in a Hard Fault at the line with msg.data = *data;

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

typedef struct canData_s {
  uint8_t d1;
  uint8_t d2;
  uint8_t d3;
  uint8_t d4;
  uint8_t d5;
  uint8_t d6;
  uint8_t d7;
  uint8_t d8; 
} canData_t;

#pragma pack(push, 1)
typedef struct msg_s {
  uint32_t stdId;
  uint32_t extId;
  uint8_t ide;
  uint8_t rtr;
  uint8_t dlc;
  canData_t data;  // 8 Bytes
  uint8_t navail;  // not available
  uint32_t timestamp;
} msg_t;
#pragma pack(pop)

void setData(canData_t *data) {
  msg_t msg;
  msg.data = *data;

  // Do something more ...
  printf("D1:%d", msg.data.d1);
  // ...
}

int main() {
  canData_t data;
  memset(&data, 0, 8);

  setData(&data);
}

Why does copying the structure by a direct assignment fail?

like image 523
eDeviser Avatar asked Feb 12 '18 11:02

eDeviser


People also ask

Can we copy one structure to another structure?

If the structures are of compatible types, yes, you can, with something like: memcpy (dest_struct, source_struct, sizeof (*dest_struct)); The only thing you need to be aware of is that this is a shallow copy.

Can struct be copied?

In C/C++, we can assign a struct (or class in C++ only) variable to another variable of same type. When we assign a struct variable to another, all members of the variable are copied to the other struct variable.


1 Answers

When you use non-standard #pragma pack you force the compiler to store the struct without any padding. The struct members before data are in groups of 4+4+3, then data at byte 11, which is misaligned.

So you force data to always be allocated misaligned, which can cause hardware exceptions on some CPUs if this is accessed as a word (32 bits). The code msg.data = *data; generated by the compiler might assume that when you copy two structs, they are always properly aligned, as that's normally the case. And the most efficient implementation of the copy would work with 32 bit chunks of data, so that's what it will use.

The question here is why this struct is packed to begin with, since it can neither be a hardware register mapping nor a data protocol mapping. Things like CAN-bus IDE and RTR are just single bits; I very much doubt any CAN controller reserves a whole 8 bit register for that. For example ST's "bxCAN" controller place these as individual bits in the CAN_TIxR register (CAN TX mailbox identifier register). Every other CAN controller on the market will behave similarly.

As for the CAN frame itself, you cannot directly memory-map that. The CAN controller will grab the raw CAN frame and place it in its own memory-mapped registers.

Either re-make this struct without padding or use the actual CAN controller registers as provided by your hardware.

like image 154
Lundin Avatar answered Sep 23 '22 14:09

Lundin