4

我遇到EXC_BAD_ACCESS了一段处理数据序列化的代码。该代码仅在设备(iPhone)上失败,而不在模拟器上失败。它也仅在某些数据类型上失败。

这是重现问题的测试代码:

template <typename T>
void test_alignment() {
    // allocate memory and record the original address
    unsigned char *origin;
    unsigned char *tmp = (unsigned char*)malloc(sizeof(unsigned short) + sizeof(T));
    origin = tmp;

    // push data with size of 2 bytes
    *((unsigned short*)tmp) = 1;
    tmp += sizeof(unsigned short);

    // attempt to push data of type T
    *((T*)tmp) = (T)1;

    // free the memory
    free(origin);
}

static void test_alignments() {
    test_alignment<bool>();
    test_alignment<wchar_t>();
    test_alignment<short>();
    test_alignment<int>();
    test_alignment<long>();
    test_alignment<long long>();   // fails on iPhone device
    test_alignment<float>();
    test_alignment<double>();      // fails on iPhone device
    test_alignment<long double>(); // fails on iPhone device
    test_alignment<void*>();
}

猜测这一定是内存对齐问题,我决定彻底了解这个问题。根据我对内存对齐的(有限)理解,当tmp提前 2 个字节时,对于对齐大于 2 个字节的数据类型,它会变得不对齐:

    tmp += sizeof(unsigned short);

但是测试代码对于int和其他人执行得很好!它只对long long,double和失败long double

检查每种数据类型的大小和对齐方式表明,失败的数据类型是具有不同sizeof__alignof值的数据类型:

iPhone 4:
bool           sizeof = 1 alignof = 1
wchar_t        sizeof = 4 alignof = 4
short int      sizeof = 2 alignof = 2
int            sizeof = 4 alignof = 4
long int       sizeof = 4 alignof = 4
long long int  sizeof = 8 alignof = 4 // 8 <> 4
float          sizeof = 4 alignof = 4
double         sizeof = 8 alignof = 4 // 8 <> 4
long double    sizeof = 8 alignof = 4 // 8 <> 4
void*          sizeof = 4 alignof = 4

iPhone Simulator on Mac OS X 10.6:
bool           sizeof = 1 alignof = 1
wchar_t        sizeof = 4 alignof = 4
short int      sizeof = 2 alignof = 2
int            sizeof = 4 alignof = 4
long int       sizeof = 4 alignof = 4
long long int  sizeof = 8 alignof = 8
float          sizeof = 4 alignof = 4
double         sizeof = 8 alignof = 8
long double    sizeof = 16 alignof = 16
void*          sizeof = 4 alignof = 4

(这些是从“C++ 数据对齐和可移植性”运行打印功能的结果)

有人能告诉我是什么导致了错误吗?差异真的是原因EXC_BAD_ACCESS吗?如果是这样,通过什么机制?

4

3 回答 3

4

这实际上很烦人,但对于我们这些在 x86 之前的世界中购买的人来说并不那么出乎意料 :-)

想到的唯一原因(这纯粹是猜测)是编译器正在“修复”您的代码以确保数据类型正确对齐,但sizeof/alignof不匹配会导致问题。我似乎记得 ARM6 架构放宽了某些数据类型的一些规则,但从未仔细研究过它,因为决定使用不同的 CPU。

(更新:这实际上是由寄存器设置控制的(因此可能是软件),所以我想即使是现代 CPU 仍然会抱怨不对齐)。

我要做的第一件事是查看生成的程序集,看看编译器是否正在填充您的短以对齐下一个(实际)数据类型(这将是令人印象深刻的)或(更有可能)预先填充实际写入之前的数据类型。

其次,找出 Cortex A8 的实际对齐要求是什么,我认为这是 iPhone4 中使用的内核。

两种可能的解决方案:

1/您可能必须将每种类型转换为一个char数组并一次传输一个字符 - 这应该有望避免对齐问题,但可能会对性能产生影响。使用memcpy可能是最好的,因为毫无疑问,它已经被编码以利用底层 CPU(例如在可能的情况下传输四字节块,在开始和结束时使用一字节块)。

2/ 对于那些不想立即放在 a 之后的数据类型,在之后short添加足够的填充short以确保它们正确对齐。例如,类似:

tmp += sizeof(unsigned short);
tmp = (tmp + sizeof(T)) % alignof(T);

tmp在尝试存储值之前,它应该前进到下一个正确对齐的位置。

稍后您需要执行相同的读取操作(我假设 short 表示正在存储的数据,因此您可以知道它是什么数据类型)。


将 OP 的最终解决方案放在完整的答案中(因此人们不必检查评论):

首先,程序集(在 Xcode 上Run menu > Debugger Display > Source and Disassembly)显示STMIA在处理 8 字节数据时使用的是指令(即long long),而不是STR指令。

接下来,“ARM 架构参考手册 ARMv7-A”(与 Cortex A8 对应的架构)的“A3.2.1 未对齐数据访问”部分声明STMIA不支持未对齐数据访问,而支持STR(取决于某些注册表设置)。

所以,问题是尺寸long long和错位。

至于解决方案,作为首发,一次一个字符是有效的。

于 2010-07-14T03:51:15.513 回答
1

这可能是 ARM 芯片的内存对齐问题。ARM 芯片无法处理未对齐的数据,并且在访问未对齐到某些边界的数据时会出现意外行为。关于 iPhone 的 ARM 芯片的对齐规则是什么,我没有想到数据,但解决这个问题的最佳方法是不要使用指针技巧戳数据。

于 2010-07-14T03:58:29.710 回答
0

每个 ARM 处理器都包含一条在特定地址加载或存储单个字的指令,以及一次加载或存储多个字的指令。一些处理器可以自动将单个未对齐的加载/存储转换为一系列两个或三个操作,但这种能力不会扩展到一次加载/存储多个字的指令。我希望 an 上的大多数操作int只会使用单字加载/存储指令[在极少数情况下,编译器可能会意识到,int连续存储的两个变量可以使用一条指令加载到寄存器中,但我不会'不要特别期待这样的优化]。上的操作long long但是,它通常会从连续的内存位置加载一对寄存器,因此会受益于使用单个指令。我没有分析最新的 ARM 芯片,但在 ARM7-TDMI 之类的东西上,两条连续的LDR指令每条需要三个周期;加载LDM两个寄存器需要四个周期。即使LDM需要在前面加上一个ADD来计算地址(LDR比 具有更多的寻址模式LDM),两条指令需要五个周期仍然比两条指令需要六个周期要好。

于 2014-05-29T18:59:40.370 回答