Arm64与x86字节对齐问题

x86比较宽容,ARM64严格 未对齐访问会崩,安全做法一般是保证对齐或用 memcpy。

x86 vs ARM64的对齐策略

常见触发 Bus Error 的场景

struct Foo {
    char a;
    uint64_t b;
};

Foo* p = (Foo*)malloc(sizeof(Foo));
p->b = 123;
// 这里 ARM64 上可能报 Bus Error

原因是,b在内存中可能没对齐到8字节边界。x86容忍,但ARM64不行。

另一个常见场景是 char 或 uint8_t 偏移访问

char buf[16];
uint64_t* p = (uint64_t*)(buf + 1);
*p = 0x12345678; // ARM64会报错

使用标准类型对齐

C++11提供alignas

alignas(8) uint64_t value;

或者对结构体:

struct alignas(8) Foo {
    char a;
    uint64_t b;
};

手动对齐内存

void* ptr;
posix_memalign(&ptr, 8, sizeof(Foo));
Foo* p = (Foo*)ptr;

不要强制类型转换未对齐地址

如果需要从 char* buffer 读取 uint64_t 用 memcpy

uint64_t value;
memcpy(&value, buf + 1, sizeof(value));

这样不会触发 Bus Error,也不会依赖对齐。

ARM64内存访问准则

  1. 64位数据要8字节对齐
  2. 32位数据要4字节对齐
  3. char与uint8_t 可以随意对齐
  4. 不要把char uint8_t 强制转换成 uint64_t* 直接读写
  5. 结构体对齐注意padding

常见踩坑示例

buffer偏移访问

char buf[16];
uint64_t* p = (uint64_t*)(buf + 1); // x86 OK, ARM64 崩
*p = 0x12345678;

安全的写法

uint64_t value = 0x12345678;
memcpy(buf + 1, &value, sizeof(value));

malloc stack地址未对齐

char* raw = (char*)malloc(13);
uint64_t* p = (uint64_t*)raw; // ARM64 崩

安全的写法

uint64_t* p;
posix_memalign((void**)&p, 8, sizeof(uint64_t));
*p = 0x12345678;
// 直接分配8的整数字节
char* raw = (char*)malloc(16);
uint64_t* p = (uint64_t*)raw;

结构体强制pack

#pragma pack(1)
struct Foo {
    char a;
    uint64_t b;
};
Foo f;
f.b = 42; // ARM64 崩

安全做法

文件网络二进制读写

char buf[8];
uint64_t val = *(uint64_t*)buf; // 未对齐 → ARM64 崩

安全做法

uint64_t val;
memcpy(&val, buf, sizeof(val));

数组大小是8字节,不代表数组起始地址一定是8字节对齐。

你需要区分:

这是两回事

char buf[8];
printf("%p\n", (void*)buf);
// 可能看到的地址是 0x1000 8字节对齐
// 也可能是 0x1001 未对齐
// 也可能是 0x1004 4字节对齐

不过对于

char buf[8];
uint64_t val = *(uint64_t*)buf;

实际上大部分编译器会把buf放在栈上,而栈本身通常16字节对齐的。所以很多时候 (char*)buf 恰好是8字节对齐的。 通常在 x86和ARM64上通常都不会崩。真正危险的是

char buf[16];
uint64_t val = *(uint64_t*)(buf+1);

总结

不要把任意 char 当成 uint32_tuint64_t*struct* 来解引用。

能用 memcpy 就不用强制转换,结构体按自然对齐,buffer 偏移读写小心。x86 可以侥幸,但 ARM64 不会放过你。

“在 x86 跑了十年,一上 ARM 就炸”的代码。人类总喜欢把运气误认为技术。