为什么这个EXC_BAD_ACCESS发生的时间很长而不是int?

时间:2010-07-14 03:06:49

标签: c++ iphone arm memory-alignment

我遇到了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 longdoublelong 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++ data alignment and portability"运行打印功能的结果)

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

3 个答案:

答案 0 :(得分:4)

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

想到的唯一原因(这是推测)是编译器“修复”您的代码以确保数据类型正确对齐但sizeof/alignof不匹配导致问题。我似乎记得ARM6架构放宽了一些数据类型的规则,但从来没有好好看看它,因为决定使用不同的CPU。

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

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

其次,找出Cortex A8的实际对齐要求,我认为它是IPhone4中使用的核心。

两种可能的解决方案:

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

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

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

在尝试存储值之前,应将tmp提升到下一个正确对齐的位置。

您需要稍后再进行相同的阅读(我假设短信表示数据已存储,因此您可以告诉它是什么数据类型。)


将OP的最终解决方案放在完整性的答案中(这样人们就不必查看评论):

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

接下来,“ARM体系结构参考手册ARMv7-A”(对应于Cortex A8的体系结构)的“A3.2.1未对齐数据访问”部分指出STR不支持未对齐数据访问{{1做(取决于某些注册表设置)。

因此,问题是STMIA的大小和错位。

至于解决方案,一次一个字符正在起作用,作为首发。

答案 1 :(得分:1)

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

答案 2 :(得分:0)

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