6

I am programming on ARM and have the following code snippet (in C):

struct data {
   int x;
   char y[640];
};
unsigned int offset = 819202;
char *buf; // buf is assigned an address elsewhere in the code
struct data * testdata = (struct data *)(buf + offset) 

I get the following compilation error:

error: cast increases required alignment of target type [-Werror=cast-align]

testdata pointer needs to point to the portion of memory that contains an array of data of type struct data. So I need a pointer so that later I can apply an index to testdata. Offset is hard coded in the program. buf is received as shared memory from another process.

For example, later in code I have:

testdata[index].x = 100;

I have seen some examples on SO but I'm not sure what is the correct way to handle this. Any suggestions ?

Jake
  • 16,329
  • 50
  • 126
  • 202
  • How does code then use `testdata`? This is important. Also recommend to post how `buffer`, offset derived. Else just use `testdata = (struct data *)(void*)(buf + offset)` and live dangerously. – chux - Reinstate Monica Feb 19 '19 at 03:41
  • I'm editing it now. – Jake Feb 19 '19 at 03:42
  • @chux I've edited the question. – Jake Feb 19 '19 at 03:46
  • Better, UV, yet still missing how `buffer, offset` derived. – chux - Reinstate Monica Feb 19 '19 at 03:47
  • Offset is hard coded in the program. Buffer is received as shared memory from another process. – Jake Feb 19 '19 at 03:48
  • @chux `testdata = (struct data *)(void*)(buf + offset)` crashes at runtime – Jake Feb 19 '19 at 03:54
  • 2
    Yes, yet the `void*` did get rid of the compilation error as that was your goal here - I did say "live dangerously." Seriously, the design of code is wrong and without detailing the code that shows "Buffer is received as shared memory from another process" and why the magic number 819202 is used, we are left with too much guess work on how to offer a correct alternative. – chux - Reinstate Monica Feb 19 '19 at 04:14
  • 1
    If you are absolutely sure you've got a correctly aligned buffer, you can suppress the warning by casting through `void*`. Otherwise you have to copy bytes between the buffer and a properly declared `struct data`. This seems like a waste of bytes, because only a small portion of the struct needs to be aligned. Another option to consider is to copy just those bytes that comprise the `int` member, and deal with the rest in-place. – n. m. could be an AI Feb 19 '19 at 04:18
  • 1
    [And this can happen if it is incorrectly aligned](https://stackoverflow.com/questions/46790550/c-undefined-behavior-strict-aliasing-rule-or-incorrect-alignment/46790815), except that I guess problems are *more*, not less likely, to happen on ARM. – Antti Haapala -- Слава Україні Feb 19 '19 at 04:56
  • @chux The crash happens when I use the pointer e.g. `testdata[index].x = 100;` – Jake Feb 19 '19 at 04:59
  • 1
    In general, if you wish to extract *int* from an arbitrary, possibly unaligned address, use *memcpy*. Compiler should be able to optimize it well, if you're worried about performance in a tight loop or something. It's better do things right. – hyde Feb 19 '19 at 05:10
  • @hyde So I'm guessing to access x, I could do (say for retrieval) .. `memcpy(&num, &testdata[index], 4);` – Jake Feb 19 '19 at 05:26
  • 1
    @Jake Yeah, except I would replace `4` with `sizeof(num)` to avoid "magic numbers" in the code. If you somehow want to ensure or communicate it is or must be 4, add static assert. – hyde Feb 19 '19 at 05:34
  • 1
    many problems with this code, you will end up maintaining this for life. Now if this is a job security thing you have done very very well... – old_timer Feb 19 '19 at 15:27
  • You have code that guarantees the lower two bits of buf are 2b10 yes? – old_timer Feb 19 '19 at 22:06
  • For now I discussed with the other developer in the team and he decided to change the offset used so that it is a multiple of 4. This way alignment is not an issue. I'm also using `memcpy` when alignment issue is suspected. – Jake Feb 19 '19 at 23:17

2 Answers2

2

The cast-align warning is triggered when you attempt to cast from a type with smaller alignment to a type with larger alignment. In this case you got from a char which has alignment 1 and a struct data which (because it contains an int) has alignment 4 (assuming a int is 4 bytes).

Rather than having a pointer to a struct point into a character array, use memcpy to copy from the buffer into an instance of the struct.

struct data testdata;
memcpy(&testdata, buf + offset, sizeof(testdata));
dbush
  • 205,898
  • 23
  • 218
  • 273
  • Actually `testdata` pointer needs to point to the portion of memory that contains an array of data of type `struct data`. So I need a pointer so that later I can apply an index to `testdata`. Is there any way to get a pointer ? – Jake Feb 19 '19 at 03:41
  • 2
    @Jake If you need to modify what's in the buffer, first `memcpy` to the struct, make changes, then `memcpy` back. That's the only way to make sure you don't run afoul of alignment requirements. – dbush Feb 19 '19 at 03:43
  • 1
    Mind that on some platforms unaligned access may rise an exception or incur in heavy performance penalties (which is why `memcpy` could not be as inefficient as it could be). So copy it or make sure the alignment of `buf` is higher by allocating it in that way. – Jack Feb 19 '19 at 04:02
2

Since buf is a pointer to char, when you write (struct data *) (buf + offset), you are saying “Take this pointer to some character, and make it a pointer to struct data.” However, a struct data is required to be aligned to a multiple of four bytes1, but your buf+offset could have any alignment. If buf+offset has the wrong alignment, it cannot be made into a correct pointer to struct data, and your compiler is advising you about this problem.

The solution is to ensure that you have an aligned address that may be used as a pointer to a struct data. Two ways to do this are:

  • Define one or more struct data objects, as with struct data MyData; or struct data MyArray[10];.
  • Allocate memory for struct data objects, as with struct data *MyPointer = malloc(SomeNumber * sizeof *MyPointer);.

With any of the above, the compiler will ensure that the memory is properly aligned for a struct data object. The former would generally be used only to work with the struct data immediately and then discard it—it does not produce an object that you can return from a function. The latter is used when you want to return the resulting struct data from a function.

Once you have these struct data objects, you need to put data into them. Preferably, you would read data into them directly instead of into buf. However, if your software design does not facilitate that, you can copy the data from buf into the struct data objects, as with one of:

memcpy(&MyData, buf+offset, sizeof MyData);

memcpy(MyArray, buf+offset, sizeof MyArray);

memcpy(MyPointer, buf+offset, SomeNumber * sizeof *MyPointer);

##Footnote##

1 Four is typical for a structure containing an int and is assumed for this example. We know the alignment requirement is greater than one due to the compiler message.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312