0

Consider this typedef:

#pragma pack(4)
typedef struct
{
  uint8  dataArea0[11];
  uint8  dataArea1[12];
  uint8  dataArea2[13];
  uint8  dataArea3[14];

} myStruct;

I have some non-2^n sized arrays that I'd like to use from other libs. From those libs these dataAreas can be cast as e.g., structs or whatever i need. The problem occurs when one of these struct members land on a non 4-byte aligned address AND contain data types that are not happy about their address alignment.

Therefore I'd like to force the alignment with the pack pragma, but this does not help (at least in the IAR compiler -- from the manual: Use this pragma directive to specify the maximum alignment of struct and union members.). I also tried to use the data_alignment pragma, but this seems to be for variables and not struct members.

Does anyone know a nice compiler trick to force the alignment of the struct members?

Quicklink to compiler manual for those interested: IAR AVR32 Compiler Ref

Edit: I ended up using this as an alternative

#define ROUND_UP_NEXT_FOUR(x) ((x + 3) & ~0x03)

typedef struct
{
  uint8  dataArea0[ROUND_UP_NEXT_FOUR(11)];
  uint8  dataArea1[ROUND_UP_NEXT_FOUR(12)];
  uint8  dataArea2[ROUND_UP_NEXT_FOUR(13)];
  uint8  dataArea3[ROUND_UP_NEXT_FOUR(14)];

} myStruct;

In this way I'm sure that the padding will take place at a 4-aligned address.

Edit2:

An example of how this can go wrong:

struct otherStruct
{
  uint16 dataBuf0;
  uint32 dataBuf1;
  uint32 dataBuf2;
  uint32 dataBuf3;
  uint32 dataBuf4[10];
};

myStruct* myStructInstance = 0x00000000; //some address
//address of this is 0x0B
struct otherStruct* oS = (struct otherStruct*) myStructInstance.dataArea1;
//we assign to a 2 byte variable that is 
//located at address that is not 2 byte aligned -> error!
os->dataBuf0 = 10; 

In this case we get a runtime error (worst (or best?) case, crash).

SupAl
  • 517
  • 3
  • 12
  • 2
    The pack directive specifies the _maximum alignment_ — it doesn't specify a minimum alignment. There isn't a way to force strings (character arrays, `uint8_t` arrays) cannot be forced to a stricter alignment than the default of `1`. You can add `uint8 dataPad1[3];` between `dataArea0` and `dataArea1` (or maybe it should be `dataPad0` instead of `dataPad1`), but that's about it. – Jonathan Leffler Sep 19 '19 at 16:13
  • Yeah, but I was looking for a nice directive. It seems that other compilers will not set it as maximum, but actual alignment. Meaning that all members will be forcred to that alignment. – SupAl Sep 19 '19 at 19:57
  • 2
    "From those libs these dataAreas can be cast as e.g., structs or whatever i need." That sounds like highly questionable code and the root of all your problems. What is the actual problem you are trying to solve with that? – Lundin Sep 20 '19 at 10:39
  • *The problem occurs when one of these struct members land on a non 4-byte aligned address AND contain data types that are not happy about their address alignment.* That sounds like you're treating something like a `char` address as an address to something else such as `int` or `float`. That would be a [strict aliasing violation](https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule) and undefined behavior. – Andrew Henle Sep 20 '19 at 11:18
  • (cont) Doing so can also violate [**6.3.2.3 Pointers**, paragraph 7](https://port70.net/~nsz/c/c11/n1570.html#6.3.2.3p7) of the C standard: "A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined. ..." – Andrew Henle Sep 20 '19 at 11:19
  • (cont) You need to post the code that has problems. – Andrew Henle Sep 20 '19 at 11:21
  • @Lundin, the actual struct with dataArea members is actually the RAM. I do not want to expose the structs of certain libs to the RAM library (or any other library). Therefore i provide a struct size #define in the header file that the RAM can use to allocate space for the specific struct in the RAM. I could of course just expose the struct, but that would mess with my encapsulation. Therefore, if i could just make sure that all the dataAreas allocated by the RAM struct were 4-byte aligned, I'd be golden. – SupAl Sep 21 '19 at 15:54
  • 1
    Casting byte arrays to other types is _not_ how you do encapsulation. Rather, it is how you create lots of strange bugs. IAR is unlikely to go strict aliasing nuts like gcc, but still. Sounds like what you should do is to place the structs in RAM but hide them behind opaque pointers or such. – Lundin Sep 21 '19 at 22:54
  • @Lundin, could you give an example of this? You would declare the RAM struct in the RAM.c file with the relevant structs and then just expose function declarations that point to specific places in RAM in the RAM header file? Or would a direct exposure of const pointers be better? – SupAl Sep 23 '19 at 08:51
  • @SupAl [Here](https://stackoverflow.com/a/35127940/584518) is a simple example of how to do opaque type with forward declaration in the header. However, you can't use malloc in embedded systems so you have to create a static memory pool as described in [Static allocation of opaque data types](https://stackoverflow.com/questions/4440476/static-allocation-of-opaque-data-types). Unless you only need 1 instance of each struct, in that case you don't need the memory pool either but just a single, local static struct. – Lundin Sep 23 '19 at 08:58
  • @Lundin, Thanks for the links. However, this is an external RAM with DMA and not an on-chip RAM, so I can't just make a static memory pool allocation. The crux of the matter is this: -- ** The external RAM needs to know the size of the struct ** ** The module itself needs to know the data structure of the struct ** ** I do not want to expose the data structure in the header ** - My solution is to expose the size in the header file and make a compiler check in the module to test whether the size in the header equals the sizeof(struct). Do you have a better solution than this for this case? – SupAl Sep 23 '19 at 09:15
  • Hmm well that's a different matter entirely. DMA over SPI or over some parallel bus? The stuff in the external RAM can't be regarded as objects unless it is memory-mapped. – Lundin Sep 23 '19 at 09:21
  • DMA over EBI. What do you mean that it can't be regarded as objects? It is memory mapped by the way. In my case I place the RAM struct at the beginning of the external RAM and expose its structure in the header file so different libraries can access their own pre-allocated struct by casting that RAM struct address as a pointer to the specific kind of struct needed. – SupAl Sep 23 '19 at 09:26

1 Answers1

1

Unfortunately the IAR AVR32 compiler does not support the _Alignas keyword. However, when IAR language extensions are enabled it supports anonymous unions and this can be used to force the alignment of individual fields of a struct. The trick is that the alignment of a union is the strictest (largest) alignment of any of its fields. Thus, by wrapping each field dataArea? in an anonymous union with a dummy field with 32-bit alignment it is possible to force the alignment of each dataArea? field to 32-bit. An example is shown below. It include both raw anonymous-union declarations as well as macro-magic to simplify declaration when the number of fields is large.

#include <stdint.h>

#define GLUE_B(x,y) x##y
#define GLUE(x,y) GLUE_B(x,y)

#define ALIGNED(FIELD, ALIGN_TYPE) union { FIELD; ALIGN_TYPE GLUE(a,__LINE__); }
#define ALIGNED32(FIELD) ALIGNED(FIELD, uint32_t)

typedef struct
{
  ALIGNED(uint8_t dataArea0[11], uint32_t);
  ALIGNED32(uint8_t dataArea1[12]);
  union { uint8_t dataArea2[13]; uint32_t a2;};
  union { uint8_t dataArea3[12]; uint32_t a3;};
} myStruct;
Johan
  • 3,667
  • 6
  • 20
  • 25