Performancing Metrics


Recap: EXC_ARM_DA_ALIGN and memory alignment errors on iPhone OS 3.2 and later
Tue, 18 May 2010 10:22 AM

I ran into an interesting problem yesterday: code that used to work fine on iPhone OS 3.0 and 3.1 started randomly throwing memory access exceptions while running on the iPad (OS version 3.2). It was a bit unnerving because it's exactly the code that I wrote for Chapter 14 of "Beginning iPhone Games Development". In a general case, here is what such piece of code might look like:

typedef struct {
  CGPoint position;
  float direction;
  float speed;
} BallInfo;

- (void)receiveData:(NSData *)data fromPeer:(NSString *)peer
    inSession:(GKSession *)session context:(void *)context
{
  BallInfo b = *(BallInfo*)[data bytes]; // <<< exception here
  // do something with 'b'
  ...
}
The exception that I got in the Device Logs looked like this:
Exception Type:  EXC_BAD_ACCESS (SIGBUS)
Exception Codes: EXC_ARM_DA_ALIGN at 0x0011db75

I agree that that piece of code won't win any awards for elegance, but it's plenty fast, which is what I was going for, given that this was a real-time multiplayer game example. I tested it extensively before putting it on paper in the book, and it worked well. But it will not work in the future without some changes.

Eventually I figured out what the problem was, after Matt Galloway (@mattjgalloway) pointed me in the right direction. In a nutshell, if you are compiling code for 3.2 and later, you need to worry about "memory alignment", particularly when dealing with data buffers and casting of something like a void* into int*, for example. For more information read this: Memory Alignment Issues on ARM Processors. Matt also explored this issue in depth on the Apple Developer Forums: Thread: EXC_ARM_DA_ALIGN.

So, how would you fix the code above to work correctly under such circumstances? Here is one possible solution:

typedef struct {
  CGPoint position;
  float direction;
  float speed;
} BallInfo;

- (void)receiveData:(NSData *)data fromPeer:(NSString *)peer
    inSession:(GKSession *)session context:(void *)context
{
  BallInfo b;
  memcpy(&b, [data bytes], sizeof(b));
  // do something with 'b'
  ...
}
The downside is that it adds the overhead of copying the buffered data into a separate memory location. Upside: the app doesn't crash :-) As of right now, I'm not 100% sure that there is no better solution, but this will do for now.

Once again, thanks to Matt Galloway (@mattjgalloway) for his insight.

-- Peter Bakhirev